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