前提
User
id: integer (uniq)
name: string (uniq)
code: integer (uniq)
というモデルに対してshowアクションを作成する際、
通常、 routes.rb
で
resources :users, only: :show
を書くことで
users/:id
なルーティングが作成されますが、
idカラムではなくname(string)やcode(integer)をURLに使用する方法を↓に書きます
name:stringをURLに使いたい場合
まずベーシックなやり方から。
routes.rb
resources :users, only: :show, param: :name
これで users/:name
でアクセスできるようになります。
users_controller.rb
パラメータが:idから:nameにかわるのでコントローラでも対応が必要です。
元がこんなコードなら
def show
@user = User.find(params[:id])
end
↓こんなかんじに変えます
def show
@user = User.find_by!(name: params[:name])
end
※
find
はレコードが見つからない場合 ActiveRecord::RecordNotFound
がraiseされますが、
find_by
はレコードが見つからない場合 nil
が返るだけなので、
例外キャッチして404などを表示するために、 ActiveRecord::RecordNotFound
がraiseされる find_by!
を使います
↓find_byとfind_by!の実装
def find_by(arg, *args)
where(arg, *args).take
rescue ::RangeError
nil
end
def find_by!(arg, *args)
where(arg, *args).take!
rescue ::RangeError
raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
@klass.name)
end
user.rb
このままだとlink_to等のヘルパーメソッドでidが使用されるため、モデルで to_param
をオーバーライドします
def to_param
name
end
(わざわざ説明する必要があるかわかりませんが、わかりやすく書くと)
def to_param
return self.name
end
と同じです。
以上がid以外のstringのカラムをURLに使用する際の常套手段ですが、
integerのカラムを使用する場合はひと手間増えます。
code:integerをURLに使いたい場合
routes.rb
resources :users, only: :show, param: :code, constraints: { code: /\d+/ }
上の例と違うのは、数値のみを受け付けるconstraintsを足している点です。
これを設定しないと、 users/1hoge
等でアクセスしても users/1
にルーティングされてしまいます。
原因は調べてないのでしりませんが、
多分内部的にintegerのカラムを検索する際に #to_i
をしているはずで、
String#to_i
は先頭から数値部分だけを数値に変換するという挙動をするから・・だと思います
'100'.to_i # => 100
'100hoge000'.to_i # => 100
users_controller.rb
ここは一緒です
def show
@user = User.find_by!(name: params[:code])
end
user.rb
def to_param
code.to_s
end
カラムはintegerなので to_s
する必要があります。
おわり
constraintsで数値以外を弾くのは結構わすれがちなので、
id以外の数値カラムをURLに使用したい場合はご注意。