#to_paramでURLにユーザーIDを表示する
はじめに
今回解説するのは実際に僕が実装したもので、その時困ったこと、そしてどう対応したのかを紹介します。
このテーマについてはもっと良い方法がありそうなので、ご存知の方はご指摘いただけると幸いです。
##デフォルトのURL
Railsでshowアクション
にルートを通すと下記のようになる。
http://localhost/users/1
http://localhost/users/2
http://localhost/users/3
これだと「ん?もしかして会員数3人しかいない?」とか思われたり、何よりセンスがない。
なので、これを下記のようにユーザーIDを表示するようにしたい。
http://localhost/users/@kamo
http://localhost/users/@buta
http://localhost/users/@tanaka
##to_param
実装は簡単で、to_param
というメソッドをオーバーライドするだけ。
def to_param
user_id
end
こうすると、モデルからパスを生成する時:id
を使ってたのが、:user_id
を使うようになってくれます。
to_paramのソースは下記のとおり。
def to_key
key = respond_to?(:id) && id
key ? [key] : nil
end
def to_param
(persisted? && key = to_key) ? key.join('-') : nil
end
上のto_key
メソッドから見てみます。最初の1行で、id
の有無、またメソッドとして呼び出せるかを確認しています。2行目で3項演算子を用いて、id
を配列に格納しています。
そして次のto_param
では、modelがDBに保存されていることを確認した後、to_key
で得たid
の配列を文字列化しています。
このto_paramはパスを生成する段階で呼ばれ、その時にパスにid
を返す流れになっています。
それをオーバライドして、user_id
が返ってくるようにしたわけです。
##問題が3点
上記のようなto_param
をオーバーライドの仕方だと問題が3点出てきました。
1,user_idにindex
,new
など登録されると困る。
2,params[:id]にuser_id
が入るのでコントローラーでの修正が必要。
3,omniauth
など、SNSの簡単認証などを使っていて、user_id
が最初に登録されない場合、パスを生成できない。
##解決したコードがこちら
def to_param
user_id ? '@' + user_id : super()
end
上記のコードではuser_id
が存在するなら、@を加えてます。
@が最初に加えられればindex
だろうが、new
だろうが怖くありません。また、super()はuser_id
を持ってないユーザーは元のto_param
の振る舞いをするようにしてます。
def set_user
if params[:id].chr == '@'
user_id = params[:id].slice!(0)
@user = User.find_by!(user_id: user_id)
else
@user = User.find(params[:id])
end
end
上記では加えた@を外してます。またuser_id
を登録してないユーザー用の分岐もかけています。
これでURLにはuser_id
が表示され、かつちゃんとユーザーのデータも読み込めます。
##まとめ
この実装してて思ったのは、「Rails wayに反しているのでは...?」となりましたが、他に方法が見当たらないのでこの方法で実装しました。(当時)
今回調べてたら、実はgemでfriendly_id
てのがあり、それでも実装できるらしいです。でもまあ、せっかくto_param
覚えたし、gemの仕組みを理解するよりこっちの方が仕組みを理解するのが簡単そうだったのでこっちを採用します。
##参考にしたの
【Rails】to_paramでURLをUser.nameにする
https://remonote.jp/rails-to-param-url-user-name