記事内容
Railsチュートリアル 第10章 10.1.1 編集フォームの演習2で手こずった部分と、その解決策に関する備忘録です。
実装の全体像
実装の全体像としては、以下のようになります。
- バージョン 5.1.6 のRails
-
users#new
とuser#edit
に、form
というパーシャルを埋め込む- ソースの実体は埋め込みRuby(erb)
- 埋め込みそのものは、erbの
render
メソッドで行う
うまくいかない実装
実装内容
以下のような実装だとうまくいきません。
まずは_form.html.erb
。form
パーシャルの実体です。
<%= form_for(@user, yield(:path)) do |f| %>
...略
<% end %>
続いてnew.html.erb
。new
ビューの実体です。
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<% provide(:path, signup_path) %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
最後はedit.html.erb
。edit
ビューの実体です。
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<% provide(:path, user_path) %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
...略
</div>
</div>
実行結果
HTTPリクエストが、500 Internal Server Errorで失敗します。
Started GET "/users/2/edit" ...略
Processing by UsersController#edit as HTML
Parameters: {"id"=>"2"}
User Load (5.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Rendering users/edit.html.erb within layouts/application
Rendered users/_form.html.erb (9.4ms)
Rendered users/edit.html.erb within layouts/application (31.0ms)
Completed 500 Internal Server Error in 115ms (ActiveRecord: 5.2ms)
ActionView::Template::Error (no implicit conversion of Symbol into Integer):
1: <%= form_for(@user, yield(:url)) do |f| %>
2: <%= render 'shared/error_messages', object: @user %>
3:
4: <%= f.label :name %>
app/views/users/_form.html.erb:1:in `_app_views_users__form_html_erb___3024067975909551957_69984483370220'
app/views/users/edit.html.erb:8:in `_app_views_users_edit_html_erb__939350102988132727_69984483174380'
エラーメッセージには、「no implicit conversion of Symbol into Integer」とあります。
そもそもno implicit conversion of Symbol into Integer
というエラーが出る理由と、その解決
no implicit conversion of Symbol into Integer
というエラーが出る理由
Railsのform_for
メソッドの最終引数は、オプションハッシュとなります。一方、今回form_for
メソッドの最終引数として与えたyield(:url)
というのはハッシュではありません。それが原因となってエラーが発生するに至るわけです。
オプションハッシュとは
Railsで広く使われるテクニックとして、「オプションハッシュ」というものがあります。「特定のメソッドにおいて、必須引数以外に0個以上の可変個の引数を取りたい」という場合に使われるテクニックです。「最後の引数がハッシュである場合、{}
は省略できる」というRubyの言語仕様をうまく活用した例といえるでしょう。
ハッシュであるため、その値は「キーと値の組」でなければなりません。
解決方法
この場合、form_for
の第2引数に与える正しいキーは:url
です。というわけで、_form.html.erb
内で以下の修正が必要となります。
- <%= form_for(@user, yield(:path)) do |f| %>
+ <%= form_for(@user, url: yield(:path)) do |f| %>
うまくいく実装
実装内容
まずは_form.html.erb
。form
パーシャルの実体です。
<%= form_for(@user, url: yield(:path)) do |f| %>
...略
<% end %>
続いてnew.html.erb
。new
ビューの実体です。
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<% provide(:path, signup_path) %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
最後はedit.html.erb
。edit
ビューの実体です。
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<% provide(:path, user_path) %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
...略
</div>
</div>
実行結果
HTTPリクエストは、200 OK で正常終了します。
Started GET "/users/2/edit" ...略
Processing by UsersController#edit as HTML
Parameters: {"id"=>"2"}
User Load (8.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Rendering users/edit.html.erb within layouts/application
Rendered shared/_error_messages.html.erb (0.5ms)
Rendered users/_form.html.erb (27.1ms)
Rendered users/edit.html.erb within layouts/application (55.7ms)
Rendered layouts/_rails_default.erb (224.7ms)
Rendered layouts/_shim.html.erb (0.4ms)
User Load (3.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Rendered layouts/_header.html.erb (8.2ms)
Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 507ms (Views: 464.5ms | ActiveRecord: 11.7ms)
なぜedit_user_path
ではなくuser_path
か
ルーティングの定義における以下の記述がポイントになります。
Rails.application.routes.draw do
# ...略
resources :users
end
resources :users
という形でルーティングが定義されていますね。この場合、Railsの仕組み上、以下の定義が丸ごと与えられます。
-
/users
に対してGET
リクエストを投げると、users#index
アクションが実行される -
/users/new
に対してGET
リクエストを投げると、users#new
アクションが実行される -
/users
に対してPOST
リクエストを投げると、users#create
アクションが実行される -
/users/:id
に対してGET
リクエストを投げると、users#show
アクションが実行される -
/users/:id/edit
に対してGET
リクエストを投げると、users#edit
アクションが実行される -
/users/:id
に対してPATCH
リクエストまたはPUT
リクエストを投げると、users#update
アクションが実行される -
/users/:id
に対してDELETE
リクエストを投げると、users#destroy
アクションが実行される
この場合、/users/:id
の内容を更新してほしければ、単にuser_path(:id)
に対してPUT
リクエストを投げればよいのです。
2.2 CRUD、動詞、アクション - Rails のルーティング - Rails ガイドも読んでみるとよいです。
まとめ
-
form_for
の第2引数はオプションハッシュであり、キーと値の組が必要となる -
form_for
のオプションハッシュ:url
には、値としてRailsの_pathヘルパーをそのまま与えることもできる - 埋め込みRubyの
provide
とyield
では、Railsの_pathヘルパーをそのままやり取りすることができる