Railsのルーティングでは、shallow: trueを使用することでURLを"浅く"することができます。使用前後のサンプルコードを用いて、そのメリット・デメリットを考察しました。
TL;DR
メリット
- URLから取得できるパラメータに冗長性がなくなる
- ポリモーフィックURL、URLヘルパーのパラメータや文字数が減る
デメリット
- パーシャルビューとしてnewとeditで共通化したform_withの使い方がトリッキーになる
考察用サンプルアプリの仕様
デザインはほとんどscaffoldのままなので、見にくいところがあるかもしれません。
- ブログ(Blog)
- ブログの一覧表示
- ブログの表示+ブログ内の記事一覧
- ブログ新規登録
- ブログ変更
- ブログ削除
- 記事(Entry)
- ブログ内の記事一覧
- 記事の表示+コメント表示
- 記事の新規登録
- 記事変更
- 記事削除
- コメント(Comment)
- コメント追加
- コメント削除
ソースコードの変化
さて、ここから、shallow: true
なしとありで、ソースコードがどのように変化したかをdiffで示していきます。
ルーティング
Rails.application.routes.draw do
- resources :blogs do
+ resources :blogs, shallow: true do
resources :entries, except: %i(index) do
resources :comments, only: %i(create destroy)
end
end
end
Prefix Verb URI Pattern Controller#Action
- blog_entry_comments POST /blogs/:blog_id/entries/:entry_id/comments(.:format) comments#create
- blog_entry_comment DELETE /blogs/:blog_id/entries/:entry_id/comments/:id(.:format) comments#destroy
+ entry_comments POST /entries/:entry_id/comments(.:format) comments#create
+ comment DELETE /comments/:id(.:format) comments#destroy
blog_entries POST /blogs/:blog_id/entries(.:format) entries#create
new_blog_entry GET /blogs/:blog_id/entries/new(.:format) entries#new
- edit_blog_entry GET /blogs/:blog_id/entries/:id/edit(.:format) entries#edit
- blog_entry GET /blogs/:blog_id/entries/:id(.:format) entries#show
- PATCH /blogs/:blog_id/entries/:id(.:format) entries#update
- PUT /blogs/:blog_id/entries/:id(.:format) entries#update
- DELETE /blogs/:blog_id/entries/:id(.:format) entries#destroy
+ edit_entry GET /entries/:id/edit(.:format) entries#edit
+ entry GET /entries/:id(.:format) entries#show
+ PATCH /entries/:id(.:format) entries#update
+ PUT /entries/:id(.:format) entries#update
+ DELETE /entries/:id(.:format) entries#destroy
blogs GET /blogs(.:format) blogs#index
POST /blogs(.:format) blogs#create
new_blog GET /blogs/new(.:format) blogs#new
shallow: trueのありなしでルーティングが浅くなります。blogsに対して設定すると、孫にあたるcommentsも影響を受けて浅くなっています。
コントローラ
set_modelする箇所が必要最低限になり、redirectするときのポリモーフィックURLが簡単になります。
EntriesController
class EntriesController < ApplicationController
- before_action :set_blog
+ before_action :set_blog, only: %i(new create)
before_action :set_entry, only: %i(show edit update destroy)
def show
@@ -16,7 +16,7 @@ class EntriesController < ApplicationController
@entry = @blog.entries.new(entry_params)
if @entry.save
- redirect_to [@blog, @entry], notice: 'Entry was successfully created.'
+ redirect_to @entry, notice: 'Entry was successfully created.'
else
render :new
end
@@ -24,7 +24,7 @@ class EntriesController < ApplicationController
def update
if @entry.update(entry_params)
- redirect_to [@blog, @entry], notice: 'Entry was successfully updated.'
+ redirect_to @entry, notice: 'Entry was successfully updated.'
else
render :edit
end
@@ -32,7 +32,7 @@ class EntriesController < ApplicationController
def destroy
@entry.destroy
- redirect_to @blog, notice: 'Entry was successfully destroyed.'
+ redirect_to @entry.blog, notice: 'Entry was successfully destroyed.'
end
private
@@ -41,7 +41,7 @@ class EntriesController < ApplicationController
end
def set_entry
- @entry = @blog.entries.find(params[:id])
+ @entry = Entry.find(params[:id])
end
diffにはありませんが、set_blog
で@blog = Blog.find(params[:blog_id]
しています。
shallowなしの場合もEntry.find
しても良いかもしれませんが、パラメータがある以上Relationshipをチェックしたくなるため、わざわざ@blog.entries.find
していました。URLに冗長性がなくなったため迷うことがなくなります。
CommentsController
@@ -1,34 +1,21 @@
class CommentsController < ApplicationController
- before_action :set_blog, :set_entry
- before_action :set_comment, only: :destroy
-
def create
+ @entry = Entry.find(params[:entry_id])
@comment = @entry.comments.new(comment_params)
@comment.save!
- redirect_to [@blog, @entry]
+ redirect_to @entry
end
def destroy
+ @comment = Comment.find(params[:id])
@comment.destroy!
- redirect_to [@blog, @entry]
+ redirect_to @comment.entry
end
private
- def set_blog
- @blog = Blog.find(params[:blog_id])
- end
-
- def set_entry
- @entry = @blog.entries.find(params[:entry_id])
- end
-
- def set_comment
- @comment = @entry.comments.find(params[:id])
- end
-
def comment_params
params.require(:comment).permit(:body)
end
三段全てset_modelしていたのがスッキリしています。
ビュー
ビューもポリモーフィックURLやURLヘルパーを使っている箇所は全体的に記述が短くなります。大体同じなのでビューは一部だけ示します。
後述するformのpartial viewは、はまりどころかもしれません。
記事一覧
@@ -14,9 +14,9 @@
<tr>
<td><%= entry.title %></td>
<td><%= entry.body %></td>
- <td><%= link_to 'Show', [blog, entry] %></td>
- <td><%= link_to 'Edit', edit_blog_entry_path(blog, entry) %></td>
- <td><%= link_to 'Destroy', [blog, entry], method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <td><%= link_to 'Show', entry %></td>
+ <td><%= link_to 'Edit', edit_entry_path(entry) %></td>
+ <td><%= link_to 'Destroy', entry, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
若干シンプルになっています。
記事の登録、編集
entries/new.html.erb
は変更なしですが、entries/edit.html.erb
は変更しました。
<h1>New Entry</h1>
<%= render 'form', blog: @blog, entry: @entry %>
<%= link_to 'Back', blog_path(@blog) %>
@@ -1,6 +1,6 @@
<h1>Editing Entry</h1>
-<%= render 'form', blog: @blog, entry: @entry %>
+<%= render 'form', blog: nil, entry: @entry %>
-<%= link_to 'Show', [@blog, @entry] %> |
-<%= link_to 'Back', blog_path(@blog) %>
+<%= link_to 'Show', @entry %> |
+<%= link_to 'Back', blog_path(@entry.blog) %>
render 'form'
のところがちょっとトリッキーで、blog: nil
とする必要があります。
なぜかというと、
new
とedit
のformは以下のようにpartial viewにしています。ところが、shallow: true
すると、createのときにはmodel: [blog, entry]
、updateのときにはmodel: entry
としなければURLが生成できず、partial viewが共通化できなくなってしまいそうですが・・・
<%= form_with(model: [blog, entry], local: true) do |form| %>
<%# 中略 %>
<% end %>
実は、form_withの内部でArray#compactされるので、nil要素は無視されます。そのため、blog: nil
として渡してあげると、editの場合はform_with(model: entry, local: true)
と同じ意味になってくれるので、同じpartial viewが使えるということになります。
ちなみに、_form.html.erbでインスタンス変数を使ってmodel: [@blog, @entry]
とすると、editswblog: nil
のようなことをしなくて良くなります。未定義のインスタンス変数はnilになるためですね。
このあたり、stackoverflowの以下の記事を参考にして作っています。
https://stackoverflow.com/questions/9772588/when-using-shallow-routes-different-routes-require-different-form-for-arguments/9944554#9944554
サンプルアプリのソースコード
shallowなしのmaster
ブランチから、shallow
ブランチを切っています。
https://github.com/youmts/shallow-sample
https://github.com/youmts/shallow-sample/tree/shallow
感想
選択肢の一つとして考慮する価値はあるかなと思いました。URLに無駄がなくなるのでうまく使えると美しく記述できそうです。
shallow: true
を知ったのは下のQiita記事が最初でした。ありがとうございました。
resources を nest するときは shallow を使うと幸せになれる