はじめに
Rails の URL で、通常の /hoge/:id
のようなルーティングと /hoge/fuga
のような名前付きルーティングを併用したいという課題がありました。
その際どのように対応したのかを備忘として残しておきます。
前提環境
- Rails 5.2.2.1
サンプル例
name
カラムのある Post
モデルを例にします。
このモデルの name
カラムに文字列が入っていれば、id
ではなく name
の文字列を URL として使うことを想定します。
-
name
が空 →/posts/1
-
name
にhoge
と入っている →/posts/hoge
方針
方針としては、Rails ガイド を参考に、以下のように進めました。
- routes で利用するパラメータ識別子修正
- ActiveRecord::Base#to_param をオーバーライド
- 名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッド作成
対応
routes で利用するパラメータ識別子修正
まず、routes のパラメータ識別子を修正します。param
オプションを使うことで可能です。
id
か name
で find したかったので、id_or_name
というパラメータ名にしています。
Rails.application.routes.draw do
# 中略
resources :posts, param: :id_or_name
# 中略
end
ActiveRecord::Base#to_param をオーバーライド
次に、ActiveRecord::Base#to_param
をオーバーライドします。
name
があれば name
を、存在しなければそのまま id
を利用する形としました。
class Post < ApplicationRecord
# 中略
def to_param
return name if name.presence
super
end
end
名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッド作成
最後に、Post
モデルに名前付きルーティング・通常ルーティングどちらでもレコードを find できるメソッドを追加します。
id
か name
でレコードを検索し、どちらかがマッチしていればそのレコードを返却します。
存在しない場合は一応 ActiveRecord::RecordNotFound
で例外処理しています。
class Post < ApplicationRecord
# 中略
def self.find_by_id_or_name(id_or_name)
if object = self.where(id: id_or_name).or(self.where(slug: id_or_name)).first
object
else
raise ActiveRecord::RecordNotFound
end
end
end
利用するときは以下のようなメソッドの呼び出し方となります。
Post.find_by_id_or_name(params[:id_or_name])
終わりに
name
重複する場合など、考慮する点はまだあるかも知れませんが、一旦こちらで対応はできました。
もっと良いやり方などありましたらコメントいただけるとありがたいです!