LoginSignup
7
4

More than 3 years have passed since last update.

Railsのroutesでshallow: trueを利用したときのメリット、デメリットを考察してみた(サンプルコード付き)

Posted at

Railsのルーティングでは、shallow: trueを使用することでURLを"浅く"することができます。使用前後のサンプルコードを用いて、そのメリット・デメリットを考察しました。

TL;DR

メリット

  • URLから取得できるパラメータに冗長性がなくなる
  • ポリモーフィックURL、URLヘルパーのパラメータや文字数が減る

デメリット

  • パーシャルビューとしてnewとeditで共通化したform_withの使い方がトリッキーになる

考察用サンプルアプリの仕様

デザインはほとんどscaffoldのままなので、見にくいところがあるかもしれません。

image.png
  • ブログ(Blog)
    • ブログの一覧表示
    • ブログの表示+ブログ内の記事一覧
    • ブログ新規登録
    • ブログ変更
    • ブログ削除
image.png
  • 記事(Entry)
    • ブログ内の記事一覧
    • 記事の表示+コメント表示
    • 記事の新規登録
    • 記事変更
    • 記事削除
image.png
  • コメント(Comment)
    • コメント追加
    • コメント削除

ソースコードの変化

さて、ここから、shallow: trueなしとありで、ソースコードがどのように変化したかをdiffで示していきます。

ルーティング

routes.rb
 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
routes
                    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

entries_controller.rb
 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

comments_controller.rb
@@ -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は、はまりどころかもしれません。

記事一覧

entries/_index.html.erb
@@ -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は変更しました。

entries/new.html.erb
<h1>New Entry</h1>

<%= render 'form', blog: @blog, entry: @entry %>

<%= link_to 'Back', blog_path(@blog) %>
entries/edit.html.erb
@@ -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とする必要があります。

なぜかというと、
neweditのformは以下のようにpartial viewにしています。ところが、shallow: trueすると、createのときにはmodel: [blog, entry]、updateのときにはmodel: entryとしなければURLが生成できず、partial viewが共通化できなくなってしまいそうですが・・・

entries/_form.html.erb
<%= 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 を使うと幸せになれる

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4