読者です 読者をやめる 読者になる 読者になる

カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまにRuby on Railsなども。

Rails 4.2 URLにid以外のカラム値を使う

Rails Ruby

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

ここまでで、適当にユーザデータを作ると、このような画面になる。
f:id:osn_th:20151219204912p:plain
詳細画面のURLは/users/<ユーザID>の形式になっている。
f:id:osn_th:20151219204915p:plain

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に変わったことは確認できる
f:id:osn_th:20151219204918p:plain
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を取得できるようになり、
詳細画面が表示できるようになった。
f:id:osn_th:20151219204923p:plain

to_paramメソッドをオーバーライド

URLをnameでアクセスできるようになったが、まだビューヘルパーが生成するURLがidのままになっているため、
このままshowやeditリンクをクリックしてもエラーとなる。
f:id:osn_th:20151219204928p:plain

その対策のため、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に遷移できる。
f:id:osn_th:20151219204937p:plain

まとめ

以上、RESTfulなインタフェースにおけるURLにid以外の項目を使う方法を見た。
結局、以下の2点の対応をすればよいことが分かった。

  • routes.rbのresourcesメソッドのparamオプションで項目を指定
  • モデルのto_paramメソッドをオーバーライドしキー項目の値を返すロジックを実装

以上!