Rails
AdventCalendar

resources を nest するときは shallow を使うと幸せになれる

More than 1 year has passed since last update.

scaffold を制するものは rails を制す、という言い伝えがありますが、 rails g scaffold User などとすると routes.rb に突如現れるのが、 resources というやつです。

いまここに、Group と User という2つのモデルがあり、 Group が has_many :users で、 User が belongs_to :group だとしましょう。よくある関係ですね。

このような場合に、resources を

config/routes.rb
resources :group do
  resources :user
end

こんな風に nest すると、新規に User を作る時の URL が /groups/:group_id/users/new となり、自然な形で group_id を渡すことができます。

ところが、このように routes を定義すると、いざ User が生成された後にその User を show するには /groups/:group_id/users/:id などとしなければならなくなってしまいます。
これは冗長ですね。 User の id が group に寄らず unique であるなら、 /users/:id で参照出来て然るべきです。

そこで、shallow です。

config/routes.rb
resources :group, shallow: true do
  resources :user
end

こうすると、なんと、

# rake routes
 group_user_index GET    /group/:group_id/user(.:format)          user#index
                  POST   /group/:group_id/user(.:format)          user#create
   new_group_user GET    /group/:group_id/user/new(.:format)      user#new
        edit_user GET    /user/:id/edit(.:format)                 user#edit
             user GET    /user/:id(.:format)                      user#show
                  PUT    /user/:id(.:format)                      user#update
                  DELETE /user/:id(.:format)                      user#destroy
      group_index GET    /group(.:format)                         group#index
                  POST   /group(.:format)                         group#create
        new_group GET    /group/new(.:format)                     group#new
       edit_group GET    /group/:id/edit(.:format)                group#edit
            group GET    /group/:id(.:format)                     group#show
                  PUT    /group/:id(.:format)                     group#update
                  DELETE /group/:id(.:format)                     group#destroy

な、なんて美しいんだ。。!

何が起きているかというと、user_id を指定しない action である index, new, create の3つは group_id を必要とし、それ以外の action では user_id のみを指定すればよい、ということに、たったの "shallow: true" だけでなってしまったのです。

もちろん、以下のように action を拡張した場合

config/routes.rb
resources :group, shallow: true do
    resources :user do
      get :search, on: :collection
      post :follow, on: :member
    end
end

には、

# rake routes
(抜粋)
search_group_user_index GET    /group/:group_id/user/search(.:format)   user#search
            follow_user POST   /user/:id/follow(.:format)               user#follow

ご覧の通り、 on: :collection で定義した :search の方にのみ、:group_id が必須になっています。
そうですそうです、そうして欲しかったんですよ!

さらに深く resources を nest した場合にも、 shallow:true は1つだけで大丈夫。

config/routes.rb
resources :group, shallow:true do
  resources :user do
    resources :entry
  end
end

この場合、entries#new は /user/:id/entries/new となり、 entries#show は /entries/:id となります。

最後に、おそらく多くの方が form でつまずくと思うので、その時はこれを読みましょう: http://stackoverflow.com/a/9944554/683157

Author: kuboon