Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 5.1〜8.0: 'form_with' APIドキュメント(翻訳)

更新情報

  • 更新(2019/09/13): Rails 6.0時点のドキュメント更新を反映。
  • 更新(2020/03/09): Rails 6で削除された項目を反映。
  • 更新(2021/01/22): Rails 6.1の変更を反映(edge APIドキュメントに基づいています)。6.1 edgeドキュメントで追加または変更されたパラグラフ冒頭には「(6.1 edge)」を追加しています。
  • 更新(2022/01/06): Rails 7の更新を反映し、(6.1 edge)を削除。
  • 更新(2023/10/24): Rails 7.1の更新を反映。
  • 更新(2024/09/06): Rails 7.2の更新を反映(サンプルコードのPostArticleに置き換え)
  • 更新(2025/01/23): Rails 8.0.1の更新を反映(text_areatextareacheck_boxcheckbox)(フォームヘルパーの概要を追加)

こんにちは、hachi8833です。form_withはRailsのビューでフォームを送信するためのヘルパーメソッドです。

Rails 5.1より前のform_forform_tagはその後非推奨になりました。Rails 5.1以降はこのform_withだけを使いましょう。

参考: Provide form_with as a new alternative to form_for/form_tag · Issue #25197 · rails/rails

概要

MITライセンスに基づいて翻訳・公開いたします。

原文の更新や訳文の誤りにお気づきの方は、@hachi8833までお知らせください。

🔗 概要: Action Viewフォームヘルパーについて

Railsのフォームヘルパーは、素のHTMLを使う場合よりもリソースをずっと簡単に操作できるように設計されています。

通常、リソースを作成または更新するように設計されたフォームは、いくつかの方法でリソースIDを反映します。

  • 1:フォームが送信されるURL(フォーム要素のaction属性)によって、リクエストが適切なコントローラーアクションにルーティングされる必要があります。
  • 2:inputフィールドは、コントローラで値が paramsハッシュ内の適切な場所に表示されるように名前を付ける必要があります(既存のリソースの場合は適切な:idパラメーターが使われます)。
  • 3:既存のレコードの場合、フォームが最初に表示されるときに、リソースの属性に対応するinputフィールドに、それらの属性の現在の値が表示されます。

Railsでは、これらをform_with(またはform_for)、およびいくつかの関連するヘルパーメソッドを用いてフォームを作成することで実現されます。これらのメソッドは適切なHTML formタグを生成し、フォームの対象となるモデルを認識するフォームビルダーオブジェクトを生成します。
入力フィールドは、フォームビルダーで定義されたメソッドを呼び出すことで作成されます。つまり、モデル属性に対応する適切な名前とデフォルト値、使いやすいIDなどを生成できます。
生成されたフィールド名の規則により、アプリ開発者が手間をかけずに、paramsで適切に構造化されたフォームデータをコントローラで受け取れるようになります。

たとえば、新しい人物(person)を作成する場合、PeopleController#newアクションでは@personの新しいインスタンスを設定し、ビューテンプレートでは以下のようにそのオブジェクトをform_with(またはform_for)に渡すのが典型的です。

<%= form_with model: @person do |f| %>
  <%= f.label :first_name %>:
  <%= f.text_field :first_name %><br />

  <%= f.label :last_name %>:
  <%= f.text_field :last_name %><br />

  <%= f.submit %>
<% end %>

上のform_withで生成されるHTMLは以下のようになります(このHTMLは整形してあります)。

<form action="/people" class="new_person" id="new_person" method="post">
  <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
  <label for="person_first_name">First name</label>:
  <input id="person_first_name" name="person[first_name]" type="text" /><br />

  <label for="person_last_name">Last name</label>:
  <input id="person_last_name" name="person[last_name]" type="text" /><br />

  <input name="commit" type="submit" value="Create Person" />
</form>

このHTMLを見ると、いくつかの場所で、フォームを送信するパスや入力フィールドの名前などのリソースに関する知識が反映されていることがわかります。

特に、生成されるフィールド名が規則に沿っているおかげで、フォームに設定された人物(person)の属性を含むネストしたハッシュparams[:person]がコントローラで取得されます。このハッシュは、以下のようにそのままPerson.newに渡せます。

@person = Person.new(params[:person])
if @person.save
  # 成功した場合の処理
else
  # エラー処理
end

興味深いことに、人物を編集するときにも上の例とまったく同じビューコードを使えます。@personが、名前が"John Smith"でIDが256である既存レコードの場合、上のコードはそのままで、代わりに以下の結果を生成します。

<form action="/people/256" class="edit_person" id="edit_person_256" method="post">
  <input name="_method" type="hidden" value="patch" />
  <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
  <label for="person_first_name">First name</label>:
  <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />

  <label for="person_last_name">Last name</label>:
  <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />

  <input name="commit" type="submit" value="Update Person" />
</form>

この編集用フォームにある「エンドポイント」「デフォルト値」「送信ボタンのラベル」には、@personにあるレコードの内容が反映されていることにご注目ください。これは、リソースが新規レコードであるかどうかがフォーム関連のヘルパーによって自動的に認識され、それに応じてHTMLを生成しているからです。

コントローラはこのフォームデータもparams[:person]で受け取り、以下のようにそのままPerson.newに渡せます。

if @person.update(params[:person])
  # success
else
  # error handling
end

以上は、リソースベースでフォームを扱う典型的な方法です。

🔗 Rails 5.1〜8.0: 'form_with' APIドキュメント(翻訳)

# API呼び出し
form_with(model: nil, scope: nil, url: nil, format: nil, **options)

URL、スコープ、モデルの組み合わせを元にformタグを作成します。

訳注(Rails 7.1〜)

#50931modelオプションにnilを渡すことが非推奨化されました。Rails 8.0からはmodelオプションにnilを渡すとArgumentErrorが発生します。

-form_with(model: nil, scope: nil, url: nil, format: nil, **options)
+form_with(model: false, scope: nil, url: nil, format: nil, **options, &block)

参考: 週刊Railsウォッチ20240221: form_withmodelオプションにnilを渡すことが非推奨化された

URLのみを指定する

<%= form_with url: articles_path do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/articles" method="post">
  <input type="text" name="title" />
</form>

訳注(Rails 7〜)

<!-- Rails 6.1まで -->
<form action="/posts" method="post" data-remote="true">

Rails 6.1までの出力結果(上)にはdata-remote="true"が含まれていますが、Rails 7の出力には含まれていません(下)。

<!-- Rails 7 -->
<form action="/posts" method="post">

意図的に空URLを渡す(Rails 7

<%= form_with url: false do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form method="post">
  <input type="text" name="title" />
</form>

inputフィールド名にスコープのプレフィックスを追加する

<%= form_with scope: :article, url: articles_path do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/articles" method="post">
  <input type="text" name="article[title]" />
</form>

渡されたモデルからURLとスコープを自動推測する

<%= form_with model: Article.new do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/articles" method="post">
  <input type="text" name="article[title]" />
</form>

既存のモデルを更新するフォームで、モデルの値をフィールドに表示する

<%= form_with model: Article.first do |form| %>
  <%= form.text_field :title %>
<% end %>
# =>
<form action="/articles/1" method="post">
  <input type="hidden" name="_method" value="patch" />
  <input type="text" name="article[title]" value="<the title of the article>" />
</form>

フォームのフィールドは、必ずしもモデルの属性と対応してなくてもよい

<%= form_with model: Cat.new do |form| %>
  <%= form.text_field :cats_dont_have_gills %>
  <%= form.text_field :but_in_forms_they_can %>
<% end %>
# =>
<form action="/cats" method="post">
  <input type="text" name="cat[cats_dont_have_gills]" />
  <input type="text" name="cat[but_in_forms_they_can]" />
</form>

フォームのパラメータは、コントローラでパラメータのネストに沿ってアクセスできます。つまり、inputフィールドにtitlearticle[title]というフィールド名がある場合、コントローラではそれぞれparams[:title]params[:article][:title]としてアクセスできます。

フォームのinputフィールド名 コントローラのparams
title params[:title]
article[title] params[:article][:title]

上述のコード例では、比較しやすさのため送信ボタンを省略しています。また、UTF-8サポートを有効にする自動生成のhiddenフィールドや、CSRF(Cross Site Request Forgery)保護に必要な認証トークンも省略しています。

リソース指向のスタイル

上述のコード例の多くでは、単にform_with:modelを渡しています。これはRESTfulなルーティングのセットに対応しており、そのほとんどはconfig/routes.rbのresourcesで定義されます。

したがって、そうしたモデルのレコードを1件渡せば、RailsがURLやメソッドを推測します。

<%= form_with model: @article do |form| %>
  ...
<% end %>

上のコードは以下のようなコードと同等になります。

<%= form_with scope: :article, url: article_path(@article), method: :patch do |form| %>
  ...
<% end %>

新規レコードについても同様です。

<%= form_with model: Article.new do |form| %>
  ...
<% end %>

上のコードは以下のようなコードと同等になります。

<%= form_with scope: :article, url: articles_path do |form| %>
  ...
<% end %>

#form_withで利用できるオプション

:url
フォームの送信先URLを指定します。
渡せる値は、url_forlink_toで渡せる値と似ています。たとえば、名前付きルートを直接渡すこともできますし、:urlなしで:scopeを渡すと、現在のURLにフォームを送信することもできます。
:method
フォーム送信時のHTTPメソッド(verb)を指定します。
通常は:get:postを指定します。
:patch:put:deleteを指定すると、隠しinput名の後ろに_methodが追加され、POST verb上でこれらのHTTP verbをシミュレートします。
:format
フォーム送信先であるルーティングのフォーマットを指定します。
:jsonなど通常と異なるリソースタイプを送信するのに便利です。
:urlがオプションに渡されている場合、このオプションはスキップされます。
:scope
inputフィールド名のプレフィックスにスコープを追加します。これにより、送信されたパラメータをコントローラでグループ化できます。
:namespace
フォームの要素でid属性を一意にする名前空間です。namespace属性で指定した名前にアンダースコアを追加したものが、生成されたHTML idの前に追加されます。
:model
:url:scopeの自動推測に使うモデルオブジェクトを指定し、inputフィールドにモデルの値を表示します。
たとえば、title属性の値が"Ahoy!"ならtitleの入力フィールドの値に"Ahoy!"と表示されます。
モデルが新しいレコードの場合は作成用フォームが生成され、モデルが既存のレコードの場合は更新用フォームが生成されます。
デフォルトの動作を上書きするには、:scope:urlを渡します(params[:article]params[:blog]に変更するなど)。
:authenticity_token
フォームで使う認証トークンを指定します。
カスタムの認証トークンを指定して上書きすることも、falseを渡して認証トークンのフィールドをスキップすることもできます。
有効なフィールドのみに制限されている支払用ゲートウェイへのような外部リソースにフォームを送信する場合に便利です。

config.action_view.embed_authenticity_token_in_remote_forms = falseを指定すると、埋め込み認証トークンがremoteフォームで省略されることがあります。この指定はフォームでフラグメントキャッシュを使う場合に便利です(remoteフォームがmetaタグから認証トークンを取得するようになるので、JavaScriptがオフになっているブラウザをサポートする場合を除けば認証トークンをフォームに埋め込む必要がなくなります)。
:local
Rails 7)標準のHTTPフォームを送信するかどうかを指定します。trueを指定すると、フォームは標準のHTTPで送信されます。falseを指定すると、フォームは「リモートフォーム」として送信され、Rails UJSによってXHRとして処理されます。

指定のない場合の振る舞いは、config.action_view.form_with_generates_remote_forms設定から導出されますが、この設定の値は実際にはlocalの値と逆です。
Rails 6.1では、この設定オプションはデフォルトでfalseになります(local: trueを渡すのと同等)。
それより前のバージョンのRailsでは、この設定オプションはデフォルトでtrueになります(local: falseを渡すのと同等)。

:skip_enforcing_utf8
trueを指定すると、送信時にutf8という名前の隠しフィールドが出力されなくなります。
:builder
フォームのビルドに使うオブジェクトをオーバーライドします。
:id
HTMLのid属性を指定します(オプション)。
:class
HTMLのclass属性を指定します(オプション)。
:data
HTMLのdata属性を指定します(オプション)。
:html
上以外のHTML属性を使う場合に指定します(オプション)。

#form_withにブロックを渡さない場合は、開始formタグを生成します。

# 名前付きパスを指定する場合
<%= form_with(model: @article, url: super_articles_path) %>

# スコープを追加する場合
<%= form_with(model: @article, scope: :blog) %>

# フォーマットを指定する場合
<%= form_with(model: @article, format: :json) %>

# トークンを無効にする場合
<%= form_with(model: @article, authenticity_token: false) %>

ルーティングをadmin_article_urlのように名前空間化する場合は以下のようにします。

<%= form_with(model: [ :admin, @article ]) do |form| %>
  ...
<% end %>

リソースに関連付けが定義されている状態で、ルーティングが正しく設定されているdocumentにcommentを追加したい場合は次のようにします。

<%= form_with(model: [ @document, Comment.new ]) do |form| %>
  ...
<% end %>

上のdocumentには@document = Document.find(params[:id])が既に与えられているとします。

更新(2020/03/09)以下はRails 6.0で削除されました。

他のフォームヘルパーと組み合わせる

#form_withではFormBuilderオブジェクトが使われていますが、単独のFormHelperのメソッドやFormTagHelperのメソッドと共存させることもできます。

<%= form_with scope: :person do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>

  <%= textarea :person, :biography %>
  <%= checkbox_tag "person[admin]", "1", @person.company.admin? %>

  <%= form.submit %>
<% end %>

同様に、FormOptionsHelperのメソッド(FormOptionsHelper#collection_selectなど)と共存させたり、DateHelperのメソッド(ActionView::Helpers::DateHelper#datetime_selectなど)と共存させることもできます。

HTTPメソッド(verb)の指定方法

以下のHTTP verbの完全な配列をoptionsハッシュに渡すことができます。

method: (:get|:post|:patch|:put|:delete)

verbがGETPOST以外の場合(この2つはHTMLフォームでネイティブでサポートされます)、フォームそのものにはPOST verbが設定され、_methodという名前の隠しinputフィールドには指定の verbが設定され、後者がサーバーで解釈されます。

HTMLオプションの設定方法

HTMLのdata-*属性はdata:ハッシュで直接渡せますが、id:class:を含む他のすべてのHTMLオプションについては次のようにhtml:ハッシュの中に置く必要があります。

<%= form_with(model: @article,
              data: { behavior: "autosave" },
              html: { name: "go" }) do |form| %>
  ...
<% end %>

上のコードから以下のHTMLが生成されます。

<form action="/articles/123" method="post" data-behavior="autosave" name="go">
  <input name="_method" type="hidden" value="patch" />
  ...
</form>

非表示のモデルidを出力しないようにする

#form_withメソッドを使うと、自動的にモデルidが隠しフィールドとしてフォームに含まれます。このモデルidは、フォームデータとそれに関連付けられているモデルとの関連を保つために使われます。

ORMシステムによってはネストしたモデルでこうしたidを使わないものもあるので、その場合は次のようにinclude_id: falseを指定することで隠しフィールドのモデルidを出力しないようにできます。

<%= form_with(model: @article) do |form| %>
  <%= form.fields(:comments, skip_id: true) do |fields| %>
    ...
  <% end %>
<% end %>

フォームビルダをカスタマイズする

FormBuilderクラスをカスタマイズしてフォームをビルドすることもできます。カスタマイズするには、FormBuilderを継承してサブクラスを作り、必要なヘルパーメソッドを定義またはオーバーライドします。

次のコード例では、フォームのinputにラベルを自動追加するヘルパーを作成済みであることが前提です。

<%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
  <%= form.text_field :first_name %>
  <%= form.text_field :last_name %>
  <%= form.textarea :biography %>
  <%= form.checkbox :admin %>
  <%= form.submit %>
<% end %>

上のようにコードを書いてから、次のコードを書きます。

<%= render form %>

これにより、people/_labelling_formというテンプレートを使ってレンダリング(=HTML生成)され、フォームビルダを参照するローカル変数の名前はlabelling_formになります。

特に指定しない限り、カスタムのFormBuilderクラスは、ネストした#fields_for呼び出しのオプションと自動的にマージされます。

上のようなコードを別のヘルパーにも含めておきたい場合、多くは以下のように書くこともできます。

def labelled_form_with(**options, &block)
  form_with(**options.merge(builder: LabellingFormBuilder), &block)
end

関連記事

Rails 6.1で form_withのデフォルトが「remoteなし」になった(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。