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