Railsでネストしたリソースを作成する際にハマったこと

Railsでwebアプリケーションを開発する際に、あるリソースの配下に別のリソースを入れ子にさせたいことはよくあると思います。

つい最近、この部分で自分が少しハマったため、まとめておきたいと思います。

ネストしたリソースとは?

例えば、以下のようにブログとコメントのmodelがあるとします。

# app/models/blog.rb
class Blog < ApplicationRecord
  has_many :comment
end

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :blog
end

routes.rb でルーティングを以下のように設定するとブログとそれに紐づくコメントという親子関係を表せるようになります。

resources :blogs do
  resources :comments
end

発行されるパスは以下のようになります。

HTTPメソッド パス コントローラ#アクション
GET /blogs/:blog_id/comments comments#index
GET /blogs/:blog_id/comments/new comments#new
POST /blogs/:blog_id/comments comments#create
GET /blogs/:blog_id/comments/:id comments#show
GET /blogs/:blog_id/comments/:id/edit comments#edit
PATCH/PUT /blogs/:blog_id/comments/:id comments#update
DELETE /blogs/:blog_id/comments/:id comments#destroy

上記を例にネストしたリソースを定義する目的の一つは親のリソースのidを入れ子になっている子のコントローラ内で外部キーとして利用が可能な点かなと考えています。

namespaceとは何が違うのか

私が整理できていなかったことの一つに namespace とネストしたリソースの使い分けがあります。

namespace でルーティングをグループ化するときは例えば以下のように宣言できます。

namespace :blogs do
  resources :comments
end

発行されるパスは以下になります。

HTTPメソッド パス コントローラ#アクション
GET /blogs/comments blogs/comments#index
GET /blogs/comments/new blogs/comments#new
POST /blogs/comments blogs/comments#create
GET /blogs/comments/:id blogs/comments#show
GET /blogs/comments/:id/edit blogs/comments#edit
PATCH/PUT /blogs/comments/:id bogs/comments#update
DELETE /blogs/comments/:id blogs/comments#destroy

ネストしたリソースの宣言との結果の違いが分かるでしょうか?

(私はこれが整理できていなかったせいでハマりました・・・)

パスとコントローラのアクションを見ていただくと

  • パスはblogsのidを持っていない
  • コントローラのアクションはグループ化したルーティングに基づいてファイルが構成されている

という違いがあります。

ハマったこと

ここまで読んでいただいた方は、なんとなくお察しかもしれませんが、私は最近ネストしたリソースを宣言して意図したコントローラに処理が入っていないことでハマりました・・・!

やっていたこととしては以下の内容です。(実際の実装ではなく例です)

HTTPメソッド パス コントローラ#アクション
GET /blogs/:blog_id/comments comments#index

この時だけ外部からのリクエストの際にログイン認証をスキップするという実装をしたかったのですが、ログイン認証に弾かれてログイン画面にリダイレクトしてしまうという事象が起きていました。

実はファイルの構成で

  • app/controllers/blogs/comments_controller.rb
  • app/ controllers/comments_controller.rb

というように comments_controller.rb はファイル構成の違いで2個あり、一つ目は認証をスキップしていて二つ目は認証が必要な実装をしていました。

もう一度表をよく見てみるとちゃんと書いてありますね。

comments#index

ネストしたリソースを宣言した際、コントローラのアクションはネストしません。

つまり、app/controllers/comments_controller.rb のアクションが実行されるということです。

Railsガイドもrails routes の結果も見ていたはずなのに・・・

ネストしたリソースで app/controllers/blogs/comments_controller.rb のアクションが実行されるようにするには以下のようにしてあげれば解決できます。

resources :blogs do
  resources :comments, controller: "blogs/comments"
end

終わりに

Railsでwebアプリケーションを開発する際に初心者は特にroutes.rbを触る機会はあまりないのではないでしょうか?

私は技術書やチュートリアルで何度か宣言を書いたことがある程度で、Railsのルーティングについては理解が怪しいままでした。

その結果、ネストしたリソースと namespace とが混ざってしまい、問題を解決するのに少し時間がかかりました。

こちらのブログが同じようにルーティングの理解が怪しい・・・という方の参考に少しでもなれば嬉しいです!

もしも内容に誤りがありましたら、コメント等をいただけると幸いです。