Rails 5.1 以上は form_for, form_tag をやめて form_with を使おう

当ブログでは form_for の f.object って何だ?(Rails) がよく閲覧されているのですが、Rails 5.1 以上では form_for, form_tag が非推奨になり、両者を統合する form_with が実装されています。
そこで、古い form_for, form_tag を利用されている方のために form_with の使い方を書きました。

開発環境

  • MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)
  • macOS Mojava バージョン 10.14.6
  • rbenv -v 1.1.2
  • ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]
  • Rails 6.0.2.1
  • postgres (PostgreSQL) 11.5

関連するモデルがあるときは、form_forとして振る舞う

$ bin/rails g scaffold Book name:string

を行って生成される views/books/_form.html.slim を見てみましょう。(erbではなく、slimを用いています)

= form_for @book do |f|
  - if @book.errors.any?
    #error_explanation
      h2 = "#{pluralize(@book.errors.count, "error")} prohibited this book from being saved:"
      ul
        - @book.errors.full_messages.each do |message|
          li = message

  .field
    = f.label :name
    = f.text_field :name
  .actions = f.submit

全く同じ役割を form_with に担わせるならば、modelオプションで対応するモデルを指定して、URLとスコープを自動推論させます。
また、form_with ではデフォルトで ajax リクエストになるので従来通りの動作にするならば、 local: true をつける必要もあります。

= form_with model: @book, local:true do |f|
  - if @book.errors.any?
    #error_explanation
      h2 = "#{pluralize(@book.errors.count, "error")} prohibited this book from being saved:"
      ul
        - @book.errors.full_messages.each do |message|
          li = message

  .field
    = f.label :name
    = f.text_field :name
  .actions = f.submit

関連するモデルがないときは、form_tagとして振る舞う

これまで、フォームを作りたいときで、関連するモデルがないときは form_tag を使ってきました。検索フォームなどです。

= form_tag books_path, method: :get do
  = text_field_tag :hoge
  = submit_tag

全く同じ役割を form_with に担わせるならば、urlオプションで対応するURLを指定します。
デフォルトで ajax リクエストになるので、同様に local: true をつける必要もあります。
また、フォームビルダー オブジェクト f をレシーバにして、f.text_field, f.submit というように form_for としての役割のときと同じ記法ができる点が特徴的です。

= form_with url: books_path, method: :get, local: true do |f|
  = f.text_field :hoge
  = f.submit

生成されるHTMLの比較

最後に、生成されるHTMLを比較してみましょう。
binding.pry を差し込んで標準出力してみます。

$ puts form_for(Book.new){ |f| f.text_field :name }
<form class="new_book" id="new_book" action="/books" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="3NGgDlJjkprAZMNNiHySf5uShGeLMCHJH0AbyLwe8W9kln+pw3vCZQhYqdrkhm4GtcDphRcsMNlArgmr3kjA2w==" />
  <input type="text" name="book[name]" id="book_name" />
</form>

$ puts form_with(model: Book.new, local: true){ |f| f.text_field :name }
<form action="/books" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="xT7KuLoJ7k6VXyHttpYZOd9jonPlpRDr78du5VEBIe99eRUfKxG+sV1jS3rabOVA8THPkXm5AfuwKXyGM1cQWw==" />
  <input type="text" name="book[name]" id="book_name" />
</form>

form要素へのidとclass属性は自動付与されなくなっていますが、input要素などのフォームフィールドへのid属性は変わらず自動付与されるようになっているようです。

参考リンク

Railsガイド Ruby on Rails 5.1 リリースノート
api.rubyonrails.org/ のActionView::Helpers::FormHelper

この記事の内容が役に立ったと思いましたら、SNSで記事を共有していただけますと幸いです。