Railsのルーティング:Shallow Nestingの完全ガイド
はじめに
Railsでネストしたリソースのルーティングを設定する際、深くネストしすぎると「overly complex and cumbersome to maintain(過度に複雑でメンテナンスが面倒)」な状態に陥ることがあります。
例えば、以下のような3階層のネストを考えてみましょう。
/publishers/1/magazines/2/photos/3
このようなURLに対応するルートヘルパーは publisher_magazine_photo_url となり、3つのオブジェクトをすべて指定する必要があります。これは非常に冗長で、コードの可読性やメンテナンス性を低下させます。
一般的な経験則として、リソースのネストは1階層までに留めることが推奨されています。
本記事では、この問題を解決する「Shallow Nesting(シャローネスティング)」パターンについて詳しく解説します。
Shallow Nestingとは
Shallow Nestingは、ネストの深さを抑えつつ、リソース間の階層関係を適切に表現するルーティング手法です。
基本的な考え方
-
コレクションアクション(
index,new,create):親リソースのスコープ下に配置し、階層関係を明示 -
メンバーアクション(
show,edit,update,destroy):ネストさせず、リソースを一意に識別できる最小限の情報でルートを構築
**「メンバーアクション」**は、個別のリソースに対して作用し、そのリソースを特定するためにIDが必要なアクション(show, edit など)を指します。
**「コレクションアクション」**は、リソース全体に対して作用するアクション(index など)を指します。
実装方法
方法1::only オプションを使った明示的な指定
resources :articles do
resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]
この方法では、コレクションアクションとメンバーアクションを明示的に分けて定義します。:only オプションで、Railsが生成するルートを限定しています。
方法2::shallow オプションを使った簡潔な記述
resources :articles do
resources :comments, shallow: true
end
この記述は方法1と全く同じルートを生成しますが、より簡潔で読みやすくなります。
方法3:親リソースレベルでの一括設定
resources :articles, shallow: true do
resources :comments
resources :quotes
end
親リソースに shallow: true を指定すると、その配下のすべての子リソースが自動的にshallowになります。複数のネストリソースがある場合に便利です。
生成されるルート
上記の設定により、以下のようなルートが生成されます。
コレクションアクション(親のIDが必要)
| HTTPメソッド | パス | アクション | ヘルパー |
|---|---|---|---|
| GET | /articles/:article_id/comments |
comments#index |
article_comments_path |
| POST | /articles/:article_id/comments |
comments#create |
article_comments_path |
| GET | /articles/:article_id/comments/new |
comments#new |
new_article_comment_path |
メンバーアクション(コメントのIDだけで識別可能)
| HTTPメソッド | パス | アクション | ヘルパー |
|---|---|---|---|
| GET | /comments/:id/edit |
comments#edit |
edit_comment_path |
| GET | /comments/:id |
comments#show |
comment_path |
| PATCH/PUT | /comments/:id |
comments#update |
comment_path |
| DELETE | /comments/:id |
comments#destroy |
comment_path |
ポイント:既存のコメントを操作する際は、どの記事のものかをパスで明示する必要がありません。データベース上でコメントのIDは一意なので、それだけで特定できるためです。
CommentsControllerの実装例
Shallow Nestingを使用した場合のコントローラ実装例を見てみましょう。
class CommentsController < ApplicationController
before_action :set_article, only: [:index, :new, :create]
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# GET /articles/:article_id/comments
def index
@comments = @article.comments
end
# GET /articles/:article_id/comments/new
def new
@comment = @article.comments.build
end
# POST /articles/:article_id/comments
def create
@comment = @article.comments.build(comment_params)
if @comment.save
redirect_to @comment, notice: 'コメントを投稿しました。'
else
render :new, status: :unprocessable_entity
end
end
# GET /comments/:id
def show
end
# GET /comments/:id/edit
def edit
end
# PATCH/PUT /comments/:id
def update
if @comment.update(comment_params)
redirect_to @comment, notice: 'コメントを更新しました。'
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /comments/:id
def destroy
@comment.destroy
redirect_to article_comments_url(@comment.article), notice: 'コメントを削除しました。'
end
private
def set_article
@article = Article.find(params[:article_id])
end
def set_comment
@comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:body, :author)
end
end
実装のポイント
-
コレクションアクション(
index,new,create):params[:article_id]から親リソースを取得 -
メンバーアクション(
show,edit,update,destroy):params[:id]から直接コメントを取得 -
before_actionで処理を分けることで、各アクションがシンプルになる
応用:shallowブロック
複数のリソースをまとめてshallowにする場合は、shallow ブロックを使うこともできます。
shallow do
resources :articles do
resources :comments
resources :quotes
end
end
この記述は、ブロック内のすべてのネストがshallowになります。
カスタマイズオプション
Shallow Nestingには2つのカスタマイズオプションがあります。
:shallow_path - パスのプレフィックスを追加
scope shallow_path: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
メンバーアクションのパスが /sekret/comments/:id のようになります。
:shallow_prefix - ルートヘルパー名にプレフィックスを追加
scope shallow_prefix: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
ヘルパー名が edit_sekret_comment_path のようになります(パス自体は変わりません)。
まとめ
Shallow Nestingを使うことで、以下のメリットが得られます。
- URLがシンプルになり、理解しやすくなる
- ルートヘルパーが簡潔になる
- メンテナンス性が向上する
- RESTfulな設計を保ちつつ、実用的なルーティングが実現できる
深くネストしたルーティングに悩んでいる場合は、ぜひShallow Nestingの導入を検討してみてください。
Discussion