LoginSignup
2
1

More than 3 years have passed since last update.

Railsチュートリアル - Webフォームのコードを共通化する際に、フォームのPOST先をどうyieldするか

Last updated at Posted at 2019-11-16

記事内容

Railsチュートリアル 第10章 10.1.1 編集フォームの演習2で手こずった部分と、その解決策に関する備忘録です。

実装の全体像

実装の全体像としては、以下のようになります。

  • バージョン 5.1.6 のRails
  • users#newuser#editに、formというパーシャルを埋め込む
    • ソースの実体は埋め込みRuby(erb)
    • 埋め込みそのものは、erbのrenderメソッドで行う

うまくいかない実装

実装内容

以下のような実装だとうまくいきません。

まずは_form.html.erbformパーシャルの実体です。

app/views/users/_form.html.erb
<%= form_for(@user, yield(:path)) do |f| %>
  ...略
<% end %>

続いてnew.html.erbnewビューの実体です。

app/views/users/new.html.erb
<% 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.erbeditビューの実体です。

app/views/users/edit.html.erb
<% 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.erbformパーシャルの実体です。

app/views/users/_form.html.erb
<%= form_for(@user, url: yield(:path)) do |f| %>
  ...略
<% end %>

続いてnew.html.erbnewビューの実体です。

app/views/users/new.html.erb
<% 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.erbeditビューの実体です。

app/views/users/edit.html.erb
<% 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

ルーティングの定義における以下の記述がポイントになります。

config/routes.rb
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のprovideyieldでは、Railsの_pathヘルパーをそのままやり取りすることができる

関連リンク

2
1
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
2
1