たとえば、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,:idand 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