34
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Railsトリビア】blog_path(@ blog)の代わりにblog_pathと書いても自動的にidが補完される

Last updated at Posted at 2021-01-09

たとえば、RailsのViewファイルとして次のようなblogs/edit.html.erbがあったとします。

<h1>Editing Blog</h1>

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

<%= link_to 'Show', blog_path(@blog) %> |
<%= link_to 'Back', blogs_path %>

5行目のblog_path(@blog)/blogs/10のようなパスを生成するヘルパーメソッドです。(ここでは@blog.idの値が10だった場合を想定)

このようにblog_pathの引数には通常、@blogのようなActiveRecordのインスタンスを渡すと思います。
ですが、blog_pathは次のように引数無しで呼びだしてもエラーにはなりません。

<%= link_to 'Show', blog_path %> |

生成されるパスは先ほどと同じく/blogs/10になります。

Viewだけでなく、コントローラ内でも同様です。

def update
  if @blog.update(blog_params)
    # blog_path(@blog) と書かなくても /blogs/10 にリダイレクトされる
    redirect_to blog_path, notice: '...'
  else
    render :edit
  end
end

Why?

上記のような編集画面は通常、以下のようなパス(URL)で呼び出されると思います。

/blogs/10/edit

blog_pathのように引数無しでヘルパーメソッドを呼び出した場合、リクエスト時のパスに含まれるid(ここでは10)を引き継いで /blogs/10 が生成されます。

引数がないとエラーになるケース

rails consoleなどでapp.blog_pathと書いた場合は引き継ぐidがないのでエラーになります。

> app.blog_path
Traceback (most recent call last):
        1: from (irb):2
ActionController::UrlGenerationError (No route matches {:action=>"show", :controller=>"blogs"}, missing required keys: [:id])
Did you mean?  blog_url
               blogs_url
               blogs_path
               new_blog_url

引数無しのblog_pathを使うのはアリか、ナシか?

個人的にはナシだと思います。

暗黙的にidが決まってしまうより、明示的に「このメソッドで生成したいパスはこのid(このオブジェクト)」ということを示すためにblog_path(@blog)のように書く方が安全だからです。

また、引数を省略するクセが付いていると、Viewのロジックによっては意図しないidでパスが生成されたりして思わぬ不具合を起こすかもしれません。

ドキュメントはどこ?

url_forメソッドのドキュメントにそれらしき記述がありました。

Missing routes keys may be filled in from the current request's parameters (e.g. :controller, :action, :id and any other parameters that are placed in the path).

(筆者訳) 不足しているルーティングのキーは、リクエストパラメータ(例 :controller や、 :action:id 、その他パスに含まれる各種パラメータ)から取得されます。

上のドキュメントに書いてあるとおり、たとえば blog_path の代わりに url_for(controller: 'blogs', action: 'show') のように書いても同じように /blogs/10 が生成されます。(もっと言えば url_for(action: 'show') だけでもOK)

他にもこんなケースが

ネストしたルーティングの場合も困った動きをします。たとえば、/blogs/10のようなページにおいて、以下のようなコードを書くとどんなURLが生成されるかわかりますか?

<%# 間違ったコード例 %>
<%= link_to 'コメント編集', edit_blog_comment_path(comment) %>

これは次のようなURLを生成します。

/blogs/(commentのid)/comments/(現在表示しているblogのid)/edit

たとえば、commentのidが3だったとすると、こんなURLになります。

/blogs/3/comments/10/edit

なぜならURIパターンが次のようになっているからです。

/blogs/:blog_id/comments/:id/edit

/:idの部分は「リクエスト時のパスに含まれるid(=現在表示しているblogのid)」をRailsが自動的に補完してくれます(補完しなくていいのに!)。そして、引数として与えたcommentのidが/:blog_idとして使われます。その結果、以下のURLが生成されるわけです。

/blogs/(commentのid)/comments/(現在表示しているblogのid)/edit

が、これは意図したURLではありません。erbのコードは本来こう書くべきです。

<%# 正しいコード例 %>
<%= link_to 'コメント編集', edit_blog_comment_path(blog, comment) %>

こうすれば以下のように適切なURLが生成されます。

/blogs/(blogのid)/comments/(commentのid)/edit
 ↓
/blogs/10/comments/3/edit

引数が1つだけでもエラーにならず、一見ちゃんと動いているように見えるのがこの問題のややこしいところです・・・😭

確認したバージョン

  • Rails 6.0.3.3
34
19
2

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
34
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?