Railsではhas_manyな関連を持つモデルに対してRESTなルートを簡単に定義できる。

1# routes.rb
2resources :blogs, only: [:index]
3  resources :posts, only: [:index]
4end

このようなルートが定義される。

1% rake routes 
2     Prefix Verb URI Pattern                      Controller#Action
3 blog_posts GET  /blogs/:blog_id/posts(.:format)  posts#index
4      blogs GET  /blogs(.:format)                 blogs#index

このときのパラメータである:blog_idにはデフォルトではBlogモデルのidが使われる。そしてこれらのルートに相当するURLは/blogs/1/postsなどとなる。

id以外のカラムをパラメータにする

例えばBlogモデルにはアプリケーション内でユニークなユーザー名のようなものを持っているとし、それをルートのパラメータにしたいというケースを考える。

これが実現すると/blogs/xoyip/postsというURLが使えるようになる。個人的には先ほど書いたものよりかっこいいと思う。よくわからない数字が入るのは好きじゃない。

やり方

Blogモデルにto_paramメソッドを定義する。

1class Blog < ActiveRecord::Base
2  def to_param
3    return user_name
4  end
5end

to_paramはActiveRecord::IntegrationモジュールのメソッドでStringを返す。モデルのインスタンスからURLを生成するときに使われるメソッドで、ソースを見ると確かにデフォルトではidを文字列にして返しているのがわかる。

1# ActiveRecord::Integration
2def to_param
3  id && id.to_s
4end

パラメータ名を変える

to_paramのオーバーライドによってURLに使うパラメータは変わったけれど、rake routesが吐き出す一覧における表記はidのままとなっていて気持ち悪い。これを変えておいたほうがあとあとわかりやすいので、こちらも変更する。

routes.rbで使うresourcesメソッドはparamという引数を取ることができる。ここにシンボルを書いておくと、パラメータの名前が書いたものに変わるという仕組みになっている。

先ほどの例でいくとこのように書くことができ、

1# routes.rb
2resources :blogs, param: :user_name, only: [:index] do
3  resources :posts, only: [:index] do
4  end
5end

rake routesの出力はこのように変わる。

1% rake routes 
2     Prefix Verb URI Pattern                             Controller#Action
3 blog_posts GET  /blogs/:blog_user_name/posts(.:format)  posts#index
4      blogs GET  /blogs(.:format)                        blogs#index

コントローラ側でパラメータを扱うときには注意が必要で、それまでparam[:blog_id]のように書いていたところをparam[:blog_user_name]などと変更する必要がある。

少し手間かもしれないが、可読性のことを考えてパラメータ名も変更しておいたほうがいい。