Rails 4.2 URLにid以外のカラム値を使う
Railsはroutes.rbでresourcesメソッドを使うことで、複数リソースに対するRESTfulインタフェースを定義できる。このとき、一つのリソースを特定するためには通常:idパラメータがデフォルトで使われるが、このidはRailsが自動採番しているため、場合によっては分かりやすさ優先でid以外のカラム値でリソースを一意に特定できるものがある場合は、そちらをURLに使いたい場合もある。
今回はそのような場合に、id以外の項目をURLに使うための変更方法をまとめてみた。
いつものように、検証した環境のバージョンを示す。
OS X EI Capitan バージョン 10.11.2 Rails 4.2.3 ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
サンプルアプリ作成
今回は例として、ユーザというモデルを考える。Railsが標準で自動採番するid列の他に、nameという列で一意の名前を持つことができるようにし、このnameを使ってidの代わりにルーティングするURLを構築する。
Userモデルをscaffoldで作成する。
$ rails g scaffold User name:string email:string
デフォルトでは:idパラメータでURLが作成される。
$ rake routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy
Userモデルのnameフィールドが一意にするために、バリデーションを追加する。
app/models/user.rb
class User < ActiveRecord::Base validates :name, uniqueness: true end
usersテーブルのnameカラムにもユニークインデックスを付与するためのマイグレーションスクリプトを作成する。
$ rails g migration add_index_users_name Running via Spring preloader in process 60967 invoke active_record create db/migrate/20151219070324_add_index_users_name.rb
add_indexでユニーク制約を付与するようにスクリプトを修正。
db/migrate/20151219070324_add_index_users_name.rb
class AddIndexUsersName < ActiveRecord::Migration def change add_index :users, :name, unique: true end end
最後にdb:migrate
$ rake db:migrate
ここまでで、適当にユーザデータを作ると、このような画面になる。
詳細画面のURLは/users/<ユーザID>の形式になっている。
URLに使うキーをidからnameに変更
routes.rbを変更
resourcesメソッドにparamオプションで:nameを指定する。
resources :users, param: :name
これでURLリソースがnameに変わった。
$ rake routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:name/edit(.:format) users#edit user GET /users/:name(.:format) users#show PATCH /users/:name(.:format) users#update PUT /users/:name(.:format) users#update DELETE /users/:name(.:format) users#destroy
ただし、この状態では以下のいずれの形式でアクセスしてもエラーとなる。
/users/aris
/users/1
※このエラー画面から、Requestパラメータがidからnameに変わったことは確認できる
user_controller.rbのset_userでユーザを特定するときにfindメソッドを使っているが、
このままでは依然としてidでリソースを特定しようとしてしまうため、nameで検索するように書き換える。
app/controllers/users_controller.rb
★変更前
def set_user @user = User.find(params[:id]) end
★変更後
def set_user @user = User.find_by(name: params[:name]) end
これで/users/arisにアクセスすることで、正しくnameからUserを取得できるようになり、
詳細画面が表示できるようになった。
to_paramメソッドをオーバーライド
URLをnameでアクセスできるようになったが、まだビューヘルパーが生成するURLがidのままになっているため、
このままshowやeditリンクをクリックしてもエラーとなる。
その対策のため、Userモデルのto_paramメソッドをオーバーライドして、nameを返すように変更する。
app/models/user.rb
class User < ActiveRecord::Base validates :name, uniqueness: true def to_param name end end
これで、link_toメソッドの遷移先URLがnameに変わった。これで正しいURLに遷移できる。
まとめ
以上、RESTfulなインタフェースにおけるURLにid以外の項目を使う方法を見た。
結局、以下の2点の対応をすればよいことが分かった。
- routes.rbのresourcesメソッドのparamオプションで項目を指定
- モデルのto_paramメソッドをオーバーライドしキー項目の値を返すロジックを実装
以上!