Action View フォームヘルパー

Webアプリケーションのフォームは、ユーザー入力で多用されるインターフェイスです。しかしフォームのマークアップは、フォームのコントロールの命名法や大量の属性を扱わなければならず、作成もメンテナンスも退屈な作業になりがちです。そこでRailsでは、フォームのマークアップを生成するビューヘルパーを提供することで作業をシンプルにしています。このガイドは、さまざまなヘルパーメソッドや、利用する時期を理解するのに役立ちます。

このガイドの内容:

  • 基本的なフォーム(検索フォームなど)の作成法
  • データベースの特定のレコードを作成・編集する、モデルベースのフォーム作成法
  • 複数の種類のデータからセレクトボックスを生成する方法
  • Railsが提供する日付時刻関連ヘルパー
  • ファイルアップロード用フォームの動作を変更する方法
  • 外部リソース向けにフォームを作成する方法とauthenticity_tokenを設定する方法
  • 複雑なフォームの作成方法

目次

  1. 基本的なフォームを作成する
  2. モデルオブジェクトを指定してフォームを作成する
  3. セレクトボックスを手軽に作成する
  4. 日付時刻フォームヘルパーを使う
  5. コレクション関連のヘルパー
  6. ファイルのアップロード
  7. フォームビルダーをカスタマイズする
  8. フォーム入力の命名規約とparamsハッシュ
  9. 複雑なフォームを作成する
  10. 外部リソース用のフォーム
  11. フォームビルダーなしで利用できるタグヘルパー
  12. form_tagform_forの利用について

このガイドはフォームヘルパーとその引数について網羅的に説明するものではありません。完全なリファレンスについては[RailsのヘルパーAPIドキュメント][]を参照してください。

1 基本的なフォームを作成する

最も基本的なフォームヘルパーはform_withです。

<%= form_with do |form| %>
  Form contents
<% end %>

上のようにform_withを引数なしで呼び出すと、<form>タグが生成されます。このフォームは、method属性の値がpostに設定され、action属性の値が現在のページに設定されます。このフォームを現在のページに送信するときにHTTP POSTメソッドが使われます。たとえば現在のページが/homeページだとすると、以下のようなHTMLが生成されます。

<form action="/home" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="Lz6ILqUEs2CGdDa-oz38TqcqQORavGnbGkG0CQA8zc8peOps-K7sHgFSTPSkBx89pQxh3p5zPIkjoOTiA_UWbQ" autocomplete="off">

このフォームに含まれているinput要素では、type属性がhiddenになっていることにご注目ください。GET以外のフォームを送信する場合は、このauthenticity_tokenの隠し入力が必要です。 このトークンは、クロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐために使われるRailsのセキュリティ機能であり、フォームヘルパーは、セキュリティ機能が有効になっていることを前提として、GET以外のすべてのフォームでこのトークンを自動的に生成します。詳しくは、Rails アプリケーションのセキュリティ保護ガイドを参照してください。

1.1 一般的な検索フォーム

検索フォームはWebでよく使われています。検索フォームには以下のものが含まれています。

  • GETメソッドを送信するためのフォーム要素
  • 入力項目を示すラベル
  • テキスト入力要素
  • 送信ボタン要素

form_withで検索フォームを作成するには、以下のように書きます。

<%= form_with url: "/search", method: :get do |form| %>
  <%= form.label :query, "Search for:" %>
  <%= form.search_field :query %>
  <%= form.submit "Search" %>
<% end %>

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

<form action="/search" accept-charset="UTF-8" method="get">
  <label for="query">Search for:</label>
  <input type="search" name="query" id="query">
  <input type="submit" name="commit" value="Search" data-disable-with="Search">
</form>

この検索フォームでは、form_withurlオプションが使われていることにご注目ください。url: "/search"を設定すると、フォームのactionの値がデフォルトの現在のページのパスからaction="/search"に変更されます。

一般に、form_withurl: my_pathを渡すと、リクエストを送信する場所がフォームで指定されます。別のオプションとして、Active Modelオブジェクトをフォームに渡す方法も使えます。これについては、モデルオブジェクトを指定してフォームを作成するで後述します。URLヘルパーを利用することも可能です。

上記の検索フォームの例では、FormBuilderオブジェクトも示されています。次のセクションでは、フォームビルダーオブジェクトが提供する多くのヘルパー(form.labelform.text_fieldなど)について学習します。

TIPS: フォームのあらゆるinput要素に対して、その名前(上記の例では"query")からid属性が生成されます。これらのIDは、CSSスタイル設定やJavaScriptによるフォームコントロールの操作で非常に有用です。

検索フォームには"GET"メソッドを使ってください。Railsでは基本的に、常にアクションに対応する適切なHTTPメソッド(verb)を選ぶことが推奨されています(訳注: セキュリティガイドにも記載されているように、たとえば更新フォームでGETメソッドを使うと重大なセキュリティホールが生じる可能性があります)。検索フォームで"GET"メソッドを使うと、ユーザーが特定の検索をブックマークできるようになります。

1.2 フォーム要素を生成するヘルパー

form_withで生成されるフォームビルダーオブジェクトには、「テキストフィールド」「チェックボックス」「ラジオボタン」などの一般的なフォーム要素を生成するためのヘルパーメソッドが多数用意されています。

これらのメソッドの第1引数は、常に入力の名前です。フォームが送信されると、この名前がフォームデータとともにparamsハッシュでコントローラに渡されるので、覚えておくと便利です。この名前は、そのフィールドにユーザーが入力した値のparamsのキーになります。

たとえば、フォームに<%= form.text_field :query %>が含まれている場合、コントローラでparams[:query]と書くことでこのフィールドの値を取得できます。

Railsでは、inputに名前を与えるときに特定の規約を利用します。これにより、配列やハッシュのような「非スカラー値」のパラメータをフォームから送信できるようになり、コントローラでもparamsにアクセス可能になります。これらの命名規約について詳しくは、本ガイドで後述する「フォーム入力の命名規約とparamsハッシュ」を参照してください。これらのヘルパーの具体的な利用法について詳しくはAPIドキュメントのActionView::Helpers::FormTagHelperを参照してください。

1.2.1 チェックボックス

チェックボックスはフォームコントロールの一種で、ユーザーが選択肢の項目をオン/オフできるようにします。チェックボックスのグループは、通常、グループから1つ以上のオプションをユーザーが選択可能にしたいときに使われます。

3つのチェックボックスがあるフォームの例を以下に示します。

<%= form.checkbox :biography %>
<%= form.label :biography, "Biography" %>
<%= form.checkbox :romance %>
<%= form.label :romance, "Romance" %>
<%= form.checkbox :mystery %>
<%= form.label :mystery, "Mystery" %>

上のコードによって以下が生成されます。

<input name="biography" type="hidden" value="0" autocomplete="off"><input type="checkbox" value="1" name="biography" id="biography">
<label for="biography">Biography</label>
<input name="romance" type="hidden" value="0" autocomplete="off"><input type="checkbox" value="1" name="romance" id="romance">
<label for="romance">Romance</label>
<input name="mystery" type="hidden" value="0" autocomplete="off"><input type="checkbox" value="1" name="mystery" id="mystery">
<label for="mystery">Mystery</label>

checkboxの第1パラメータnameは、paramsハッシュで見つかる入力の名前です。ユーザーが「Biography」チェックボックスのみをチェックした場合、paramsハッシュには次の内容が含まれます。

{
  "biography" => "1",
  "romance" => "0",
  "mystery" => "0"
}

params[:biography]で、ユーザーがそのチェックボックスを選択しているかどうかを確認できます。

チェックボックスの値(paramsに表示される値)は、オプションでchecked_valueパラメータとunchecked_valueパラメータで指定できます。詳しくは、APIドキュメントのcheckboxを参照してください。

また、collection_checkboxesも利用できます。これについてはコレクション関連のヘルパーセクションで学習できます。

1.2.2 ラジオボタン

ラジオボタンは、リストから1個の項目だけを選択できるフォームコントロールです。

たとえば、以下のラジオボタンではアイスクリームの好みのフレーバーを選択できます。

<%= form.radio_button :flavor, "chocolate_chip" %>
<%= form.label :flavor_chocolate_chip, "Chocolate Chip" %>
<%= form.radio_button :flavor, "vanilla" %>
<%= form.label :flavor_vanilla, "Vanilla" %>
<%= form.radio_button :flavor, "hazelnut" %>
<%= form.label :flavor_hazelnut, "Hazelnut" %>

出力されるHTMLは以下のようになります。

<input type="radio" value="chocolate_chip" name="flavor" id="flavor_chocolate_chip">
<label for="flavor_chocolate_chip">Chocolate Chip</label>
<input type="radio" value="vanilla" name="flavor" id="flavor_vanilla">
<label for="flavor_vanilla">Vanilla</label>
<input type="radio" value="hazelnut" name="flavor" id="flavor_hazelnut">
<label for="flavor_hazelnut">Hazelnut</label>

radio_buttonの第2パラメータは、inputの値(value)です。2つのラジオボタン項目は同じ名前(flavor)を共有しているので、ユーザーは一方の値だけを選択できます。これにより、params[:flavor]の値は"chocolate_chip""vanilla"hazelnutのいずれかになります。

チェックボックスやラジオボタンには必ずラベルも表示しておきましょう。ラベルのfor属性でオプションとラベル名を関連付けておけば、ラベルの部分もクリック可能になるのでユーザーにとって使いやすくなります。

1.3 その他のヘルパー

これまで紹介した他にも、テキスト用、メールアドレス用、パスワード用、日付時刻用など、多くのフォームコントロールを利用できます。以下の例では、これらのヘルパーの利用例と生成されるHTMLを示します。

日付時刻関連のヘルパー:

<%= form.date_field :born_on %>
<%= form.time_field :started_at %>
<%= form.datetime_local_field :graduation_day %>
<%= form.month_field :birthday_month %>
<%= form.week_field :birthday_week %>

上の出力は以下のようになります。

<input type="date" name="born_on" id="born_on">
<input type="time" name="started_at" id="started_at">
<input type="datetime-local" name="graduation_day" id="graduation_day">
<input type="month" name="birthday_month" id="birthday_month">
<input type="week" name="birthday_week" id="birthday_week">

特殊フォーマット用ヘルパー:

<%= form.password_field :password %>
<%= form.email_field :address %>
<%= form.telephone_field :phone %>
<%= form.url_field :homepage %>

上の出力は以下のようになります。

<input type="password" name="password" id="password">
<input type="email" name="address" id="address">
<input type="tel" name="phone" id="phone">
<input type="url" name="homepage" id="homepage">

その他のよく使われるヘルパー:

<%= form.textarea :message, size: "70x5" %>
<%= form.hidden_field :parent_id, value: "foo" %>
<%= form.number_field :price, in: 1.0..20.0, step: 0.5 %>
<%= form.range_field :discount, in: 1..100 %>
<%= form.search_field :name %>
<%= form.color_field :favorite_color %>

上の出力は以下のようになります。

<textarea name="message" id="message" cols="70" rows="5"></textarea>
<input value="foo" autocomplete="off" type="hidden" name="parent_id" id="parent_id">
<input step="0.5" min="1.0" max="20.0" type="number" name="price" id="price">
<input min="1" max="100" type="range" name="discount" id="discount">
<input type="search" name="name" id="name">
<input value="#000000" type="color" name="favorite_color" id="favorite_color">

隠し属性(type="hidden")付きのinputはユーザーには表示されませんが、テキスト入力を保持します。隠しフィールドに含まれている値はJavaScriptで変更される可能性があります。

パスワード入力フィールドを使っている場合は、利用目的にかかわらず、入力されたパスワードをRailsのログに残さないようにしておきましょう。方法についてはセキュリティガイドを参照してください。

2 モデルオブジェクトを指定してフォームを作成する

2.1 フォームをオブジェクトに結び付ける

form_withヘルパーの:modelオプションを使うと、フォームビルダーオブジェクトをモデルオブジェクトに紐付けできるようになります。つまり、フォームはそのモデルオブジェクトを対象とし、そのモデルオブジェクトの値がフォームのフィールドに自動入力されるようになります。

たとえば、以下のような@bookというモデルオブジェクトがあるとします。

@book = Book.find(42)
# => #<Book id: 42, title: "Walden", author: "Henry David Thoreau">

新しいbookを作成するフォームは以下のようになります。

<%= form_with model: @book do |form| %>
  <div>
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>
  <div>
    <%= form.label :author %>
    <%= form.text_field :author %>
  </div>
  <%= form.submit %>
<% end %>

HTML出力は以下のようになります。

<form action="/books" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="ChwHeyegcpAFDdBvXvDuvbfW7yCA3e8gvhyieai7DhG28C3akh-dyuv-IBittsjPrIjETlQQvQJ91T77QQ8xWA" autocomplete="off">
  <div>
    <label for="book_title">Title</label>
    <input type="text" name="book[title]" id="book_title">
  </div>
  <div>
    <label for="book_author">Author</label>
    <input type="text" name="book[author]" id="book_author">
  </div>
  <input type="submit" name="commit" value="Create Book" data-disable-with="Create Book">
</form>

form_withでモデルオブジェクトを使うと以下のような重要な処理が自動的に行われます。

  • フォームのaction属性には適切な値action="/books"が自動的に入力されます。書籍を更新する場合はaction="/books/42"のようになります。
  • フォームのフィールド名はbook[...]でスコープ化されます。つまり、params[:book]はこれらのフィールドの値をすべて含むハッシュになります。入力名の重要性について詳しくは、本ガイドのフォーム入力の命名規約とparamsハッシュの章を参照してください。
  • 送信ボタンには、適切なテキスト値(この場合は「Create Book」)が自動的に入力されます。

通常、フォーム入力にはモデル属性が反映されますが、必ずしもそうである必要はありません。モデル属性以外にも必要な情報がある場合は、フォームにフィールドを含めておけば、params[:book][:my_non_attribute_input]のようにアクセスできます。

2.1.1 複合主キーを使うフォーム

モデルで複合主キー(composite primary key)が使われている場合は、同じフォームビルダー構文から得られる出力が少し異なります。

複合主キー[:author_id, :id]を持つ@bookモデルオブジェクトの場合を例にします。

@book = Book.find([2, 25])
# => #<Book id: 25, title: "Some book", author_id: 2>

以下のフォームを作成します。

<%= form_with model: @book do |form| %>
  <%= form.text_field :title %>
  <%= form.submit %>
<% end %>

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

<form action="/books/2_25" method="post" accept-charset="UTF-8" >
  <input name="authenticity_token" type="hidden" value="ChwHeyegcpAFDdBvXvDuvbfW7yCA3e8gvhyieai7DhG28C3akh-dyuv-IBittsjPrIjETlQQvQJ91T77QQ8xWA" />
  <input type="text" name="book[title]" id="book_title" value="Some book" />
  <input type="submit" name="commit" value="Update Book" data-disable-with="Update Book">
</form>

生成されたURLには、author_ididの値が2_25のようにアンダースコア区切りの形で含まれていることにご注目ください。送信後、コントローラーはパラメータから個別の主キーの値を抽出して、単一の主キーと同様にレコードを更新できます。

2.1.2 fields_forヘルパー

fields_forヘルパーは、同じフォーム内の関連モデルオブジェクトのフィールドをレンダリングするのに使われます。通常、関連付けられている「内部の」モデルはActive Recordの関連付けを介して「メインの」フォームモデルに関連付けられます。たとえば、関連付けられているContactDetailモデルを持つPersonモデルがある場合、以下のように両方のモデルの入力を含む単一のフォームを作成できます。

<%= form_with model: @person do |person_form| %>
  <%= person_form.text_field :name %>
  <%= fields_for :contact_detail, @person.contact_detail do |contact_detail_form| %>
    <%= contact_detail_form.text_field :phone_number %>
  <% end %>
<% end %>

上のコードから以下のHTML出力が得られます。

<form action="/people" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="..." autocomplete="off" />
  <input type="text" name="person[name]" id="person_name" />
  <input type="text" name="contact_detail[phone_number]" id="contact_detail_phone_number" />
</form>

fields_forで生成されるオブジェクトは、form_withで生成されるのと同様のフォームビルダーです。 fields_forヘルパーは同様のバインディングを作成しますが、<form>タグはレンダリングされません。field_forについて詳しくは、APIドキュメントを参照してください。

2.2 レコード識別を利用する

RESTfulなリソースを扱っている場合、レコード識別(record identification)を使うとform_withの呼び出しがはるかに簡単になります。これは、モデルのインスタンスを渡すだけで、後はRailsがそこからモデル名など必要な情報を取り出して処理してくれるというものです。以下の例では、長いバージョンと短いバージョンのどちらを使っても同じ出力を得られます。

# 長いバージョン:
form_with(model: @article, url: articles_path)
# 短いバージョン(レコード識別を利用):
form_with(model: @article)

同様に、以下のように既存の記事を編集する場合、form_withの長いバージョンと短いバージョンのどちらを使っても同じ出力を得られます。

# 長いバージョン:
form_with(model: @article, url: article_path(@article), method: "patch")
# 短いバージョン(レコード識別を利用):
form_with(model: @article)

短い方のform_with呼び出し構文は、レコードの作成・編集のどちらでもまったく同じである点が便利です。レコード識別では、レコードが新しいかどうかをrecord.persisted?で自動的に識別します。さらに送信用の正しいパスを選択し、オブジェクトのクラスに基づいた名前も選択してくれます。

これは、ルーティングファイルでArticleモデルがresources :articlesで宣言されていることを前提としています。

単数形リソースを使う場合は、form_withが機能するために以下のようにresourceresolveを呼び出す必要があります。

resource :article
resolve("Article") { [:article] }

リソースを宣言すると、いくつかの副作用があります。リソースの設定や利用方法について詳しくは、ルーティングガイドを参照してください。

モデルで単一テーブル継承(STI: single-table inheritance)を使っている場合、親クラスがリソースを宣言されていてもサブクラスでレコード識別を利用できません。その場合は:url:scope(モデル名)を明示的に指定する必要があります。

2.2.1 名前空間を扱う

名前空間付きのルーティングがある場合、form_withでそのためのショートカットを利用できます。たとえば、アプリケーションにadmin名前空間がある場合は、以下のように書けます。

form_with model: [:admin, @article]

上のコードはそれによって、admin名前空間内にあるArticlesControllerに送信するフォームを作成します。つまり、更新の場合はadmin_article_path(@article)に送信します。

名前空間のレベルが増えた場合も、同様の構文で書けます。

form_with model: [:admin, :management, @article]

Railsのルーティングシステムおよび関連するルールについて詳しくはルーティングガイドを参照してください。

2.3 フォームにおけるPATCH・PUT・DELETEメソッドの動作

RailsフレームワークはRESTfulな設計を推奨しています。つまり、アプリケーション内のフォームはGETPOSTの他に、methodPATCHPUT、またはDELETEであるリクエストを作成します。しかし、HTMLフォーム自体は、フォームの送信に関してGETPOST以外のHTTPメソッドをサポートしていません

そこでRailsでは、これらのメソッドをPOSTメソッド上でエミュレートする形でこの制約を回避しています。具体的には、フォームのHTMLに"_method"という名前の隠し入力を追加し、使いたいHTTPメソッドをここで指定します。

form_with(url: search_path, method: "patch")

上のコードから以下のHTML出力が得られます。

<form action="/search" accept-charset="UTF-8" method="post">
  <input type="hidden" name="_method" value="patch" autocomplete="off">
  <input type="hidden" name="authenticity_token" value="R4quRuXQAq75TyWpSf8AwRyLt-R1uMtPP1dHTTWJE5zbukiaY8poSTXxq3Z7uAjXfPHiKQDsWE1i2_-h0HSktQ" autocomplete="off">
<!-- ... -->
</form>

Railsは、POSTされたデータを解析する際にこの特殊な_methodパラメータをチェックし、リクエストのHTTPメソッドが_methodの値として指定されているもの(この場合はPATCH)であるかのように振る舞います。

formmethod:キーワードを指定すると、フォームをレンダリングするときに送信ボタンが指定のmethod属性をオーバーライドできるようになります。

<%= form_with url: "/posts/1", method: :patch do |form| %>
  <%= form.button "Delete", formmethod: :delete, data: { confirm: "Are you sure?" } %>
  <%= form.button "Update" %>
<% end %>

<form>要素の場合と同様、ほとんどのブラウザはformmethodで宣言されるGETPOST以外のフォームメソッドをサポートしていません

Railsでは、POSTメソッド上でこれらのメソッドをエミュレートする形でこの問題を回避しています。具体的には、formmethodvaluename属性を組み合わせることでエミュレートします。

<form accept-charset="UTF-8" action="/posts/1" method="post">
  <input name="_method" type="hidden" value="patch" />
  <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
  <!-- ... -->

  <button type="submit" formmethod="post" name="_method" value="delete" data-confirm="Are you sure?">Delete</button>
  <button type="submit" name="button">Update</button>
</form>

上の場合、「Update」ボタンはPATCHメソッドとして扱われ、「Delete」ボタンはDELETEメソッドとして扱われます。

3 セレクトボックスを手軽に作成する

セレクトボックス(ドロップダウンリストとも呼ばれます)を使うと、ユーザーがオプションリストから項目を選択できるようになります。セレクトボックスのHTMLには、選択するオプションごとに1個の<option>要素を書かなければならないので、かなりの量のマークアップが必要になります。Railsには、このマークアップを生成するヘルパーメソッドが用意されています。

たとえば、ユーザーに選択して欲しい都市名のリストがあるとします。selectヘルパーを使うと以下のようにセレクトボックスを作成できます。

<%= form.select :city, ["Berlin", "Chicago", "Madrid"] %>

上のコードで以下のHTMLが出力されます。

<select name="city" id="city">
  <option value="Berlin">Berlin</option>
  <option value="Chicago">Chicago</option>
  <option value="Madrid">Madrid</option>
</select>

選択の結果は、他のパラメータと同様にparams[:city]で取得できます。

セレクトボックスのラベル(表示名)と異なる<option>値を指定することも可能です。

<%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]] %>

上のコードで以下のHTMLが出力されます。

<select name="city" id="city">
  <option value="BE">Berlin</option>
  <option value="CHI">Chicago</option>
  <option value="MD">Madrid</option>
</select>

こうすることで、ユーザーには完全な都市名が表示されますが、params[:city]"BE""CHI""MD"のいずれかの値になります。

最後に、:selected引数を使うとセレクトボックスのデフォルト値も指定できます。

<%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]], selected: "CHI" %>

上のコードで以下のHTMLが出力されます。

<select name="city" id="city">
  <option value="BE">Berlin</option>
  <option value="CHI" selected="selected">Chicago</option>
  <option value="MD">Madrid</option>
</select>

3.1 セレクトボックス用のオプショングループ

場合によっては、関連するオプションをグループ化してユーザーエクスペリエンスを向上させたいことがあります。これは、以下のようにselectHash(または同等のArray)を渡すことで行なえます。

<%= form.select :city,
      {
        "Europe" => [ ["Berlin", "BE"], ["Madrid", "MD"] ],
        "North America" => [ ["Chicago", "CHI"] ],
      },
      selected: "CHI" %>

上のコードで以下のHTMLが出力されます。

<select name="city" id="city">
  <optgroup label="Europe">
    <option value="BE">Berlin</option>
    <option value="MD">Madrid</option>
  </optgroup>
  <optgroup label="North America">
    <option value="CHI" selected="selected">Chicago</option>
  </optgroup>
</select>

3.2 セレクトボックスをモデルオブジェクトに紐づける

セレクトボックスも、他のフォームコントロールと同様にモデル属性に紐づけ可能です。たとえば、以下の@personというモデルオブジェクトがあるとします。

@person = Person.new(city: "MD")

以下はそのフォームです。

<%= form_with model: @person do |form| %>
  <%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]] %>
<% end %>

セレクトボックスのHTML出力は以下のようになります。

<select name="person[city]" id="person_city">
  <option value="BE">Berlin</option>
  <option value="CHI">Chicago</option>
  <option value="MD" selected="selected">Madrid</option>
</select>

唯一の違いは、選択されたオプションがparams[:city]ではなくparams[:person][:city]にあることです。

selected="selected"が適切なオプションに自動的に追加されている点にご注目ください。このセレクトボックスはモデルに紐付けられているので、:selected引数を指定する必要はありません。

4 日付時刻フォームヘルパーを使う

前述したdate_fieldヘルパーやtime_fieldヘルパーに加えて、Railsはプレーンなセレクトボックスをレンダリングする日付および時刻の代替フォームヘルパーも提供します。date_selectヘルパーは、年/月/日などの一時コンポーネントごとにセレクトボックスをレンダリングします。

たとえば、以下のような@personというモデルオブジェクトがあるとします。

@person = Person.new(birth_date: Date.new(1995, 12, 21))

以下はそのフォームです。

<%= form_with model: @person do |form| %>
  <%= form.date_select :birth_date %>
<% end %>

セレクトボックスのHTML出力は以下のようになります。

<select name="person[birth_date(1i)]" id="person_birth_date_1i">
  <option value="1990">1990</option>
  <option value="1991">1991</option>
  <option value="1992">1992</option>
  <option value="1993">1993</option>
  <option value="1994">1994</option>
  <option value="1995" selected="selected">1995</option>
  <option value="1996">1996</option>
  <option value="1997">1997</option>
  <option value="1998">1998</option>
  <option value="1999">1999</option>
  <option value="2000">2000</option>
</select>
<select name="person[birth_date(2i)]" id="person_birth_date_2i">
  <option value="1">January</option>
  <option value="2">February</option>
  <option value="3">March</option>
  <option value="4">April</option>
  <option value="5">May</option>
  <option value="6">June</option>
  <option value="7">July</option>
  <option value="8">August</option>
  <option value="9">September</option>
  <option value="10">October</option>
  <option value="11">November</option>
  <option value="12" selected="selected">December</option>
</select>
<select name="person[birth_date(3i)]" id="person_birth_date_3i">
  <option value="1">1</option>
  ...
  <option value="21" selected="selected">21</option>
  ...
  <option value="31">31</option>
</select>

フォームが送信されたときのparamsハッシュには、完全な日付を含む単一の値が存在しない点にご注目ください。代わりに、"birth_date(1i)"のような特殊な名前を持つ複数の値が存在します。しかしActive Modelは、モデル属性の宣言された型に基づいて、これらの特殊な名前を持つ値を完全な日付や時刻として組み立てる方法を知っています。つまり、フォームで完全な日付を表す1個のフィールドを使う場合と同じように、params[:person]Person.newPerson#updateなどに渡せるということです。

Railsでは、date_selectヘルパーの他に、time_selectヘルパー(時や分のセレクトボックスを出力する)やdatetime_selectヘルパー(日付と時刻のセレクトボックスの組み合わせ)も提供しています。

4.1 個別の日付・時刻コンポーネント用のセレクトボックス

Railsでは、個別の日付時刻コンポーネント向けのセレクトボックスをレンダリングするヘルパーとしてselect_yearselect_monthselect_dayselect_hourselect_minuteselect_secondも提供しています。

これらのヘルパーは「素の」メソッドなので、フォームビルダーのインスタンスでは呼び出されません。たとえば以下のようにselect_yearヘルパーを使うとします。

<%= select_year 2024, prefix: "party" %>

セレクトボックスのHTML出力は以下のようになります。

<select id="party_year" name="party[year]">
  <option value="2019">2019</option>
  <option value="2020">2020</option>
  <option value="2021">2021</option>
  <option value="2022">2022</option>
  <option value="2023">2023</option>
  <option value="2024" selected="selected">2024</option>
  <option value="2025">2025</option>
  <option value="2026">2026</option>
  <option value="2027">2027</option>
  <option value="2028">2028</option>
  <option value="2029">2029</option>
</select>

各ヘルパーでは、デフォルト値として数値ではなくDateオブジェクトやTimeオブジェクトを指定できます(たとえば、上記の代わりに<%= select_year Date.today, prefix: "party" %>)。ここから適切な日付と時刻の部分が抽出されて使われます。

4.2 タイムゾーンを選択する

ユーザーにどのタイムゾーンにいるのかを尋ねる必要がある場合は、非常に便利なtime_zone_selectヘルパーが使えます。

通常は、ユーザーが選択可能なタイムゾーンオプションのリストを提供する必要があります。定義済みのActiveSupport::TimeZoneオブジェクトのリストがなければ作業が面倒になる可能性があります。time_with_zoneヘルパーはこのリストをラップしているので、次のように書けます。

<%= form.time_zone_select :time_zone %>

上のコードから、以下のHTMLが出力されます。

<select name="time_zone" id="time_zone">
  <option value="International Date Line West">(GMT-12:00) International Date Line West</option>
  <option value="American Samoa">(GMT-11:00) American Samoa</option>
  <option value="Midway Island">(GMT-11:00) Midway Island</option>
  <option value="Hawaii">(GMT-10:00) Hawaii</option>
  <option value="Alaska">(GMT-09:00) Alaska</option>
  ...
  <option value="Samoa">(GMT+13:00) Samoa</option>
  <option value="Tokelau Is.">(GMT+13:00) Tokelau Is.</option>
</select>

5 コレクション関連のヘルパー

Railsで任意のオブジェクトのコレクションから選択肢のセットを生成する必要がある場合は、collection_selectcollection_radio_button、およびcollection_checkboxesヘルパーが利用できます。

これらのヘルパーの有用さを示すために、Cityモデルと、それに対応するPersonモデルとの間にbelongs_to :city関連付けがある場合を考えます。

class City < ApplicationRecord
end

class Person < ApplicationRecord
  belongs_to :city
end

データベースには以下の都市名が保存されているとします。

City.order(:name).map { |city| [city.name, city.id] }
# => [["Berlin", 1], ["Chicago", 3], ["Madrid", 2]]

これで、ユーザーは以下のようなフォームで都市名をデータベースから選択できるようになります。

<%= form_with model: @person do |form| %>
  <%= form.select :city_id, City.order(:name).map { |city| [city.name, city.id] } %>
<% end %>

上のコードから、以下のHTMLが出力されます。

<select name="person[city_id]" id="person_city_id">
  <option value="1">Berlin</option>
  <option value="3">Chicago</option>
  <option value="2">Madrid</option>
</select>

上のコード例は、選択肢を手動で生成する方法を示していますが、Railsには明示的に反復処理を書かずにコレクションから選択肢を生成するヘルパーがあります。これらのヘルパーは、コレクション内の各オブジェクトで指定のメソッドを呼び出すことによって、各選択肢の値とテキストラベルを決定します。

belongs_to関連付けのフィールドをレンダリングするときは、関連付け自体の名前ではなく、外部キー名(上の例ではcity_id)を指定しなければなりません。

5.1 collection_selectヘルパー

collection_selectヘルパーを使えば、以下のように都市名を選択するセレクトボックスを生成できます。

<%= form.collection_select :city_id, City.order(:name), :id, :name %>

セレクトボックスのHTML出力は以下のように手書きの場合と同じになります。

<select name="person[city_id]" id="person_city_id">
  <option value="1">Berlin</option>
  <option value="3">Chicago</option>
  <option value="2">Madrid</option>
</select>

引数の順序は、collection_selectの場合とselectの場合で異なっていることにご注意ください。collection_selectでは、第1引数に値のメソッド(上の例では:id)、第2引数にテキストラベルのメソッド(上の例では:name)を指定します。selectヘルパーで選択肢を指定する場合の引数の順序はこれと逆である点にご注意ください(テキストラベルが最初で次が値)。前述のコード例では["Berlin", 1]となります。

5.2 collection_radio_buttonsヘルパー

ラジオボタンのセットを生成するには、collection_radio_buttonsヘルパーを使います。

<%= form.collection_radio_buttons :city_id, City.order(:name), :id, :name %>

ラジオボタンのHTML出力は以下のようになります。

<input type="radio" value="1" name="person[city_id]" id="person_city_id_1">
<label for="person_city_id_1">Berlin</label>

<input type="radio" value="3" name="person[city_id]" id="person_city_id_3">
<label for="person_city_id_3">Chicago</label>

<input type="radio" value="2" name="person[city_id]" id="person_city_id_2">
<label for="person_city_id_2">Madrid</label>

5.3 collection_checkboxesヘルパー

たとえばhas_and_belongs_to_many関連付けをサポートする形でチェックボックスのセットを生成するには、collection_checkboxesヘルパーを使います。

<%= form.collection_checkboxes :interest_ids, Interest.order(:name), :id, :name %>

チェックボックスのHTML出力は以下のようになります。

<input type="checkbox" name="person[interest_id][]" value="3" id="person_interest_id_3">
<label for="person_interest_id_3">Engineering</label>

<input type="checkbox" name="person[interest_id][]" value="4" id="person_interest_id_4">
<label for="person_interest_id_4">Math</label>

<input type="checkbox" name="person[interest_id][]" value="1" id="person_interest_id_1">
<label for="person_interest_id_1">Science</label>

<input type="checkbox" name="person[interest_id][]" value="2" id="person_interest_id_2">
<label for="person_interest_id_2">Technology</label>

訳注: collection_check_boxescollection_checkboxesにリネームされました。従来のcollection_check_boxesはエイリアスとして残されていますが、新しいコードではcollection_checkboxesを使う方が良いでしょう。

6 ファイルのアップロード

ファイルのアップロードはフォームでよく行われるタスクの1つです(アバター画像のアップロードや、処理したいCSVファイルのアップロードなど)。file_fieldヘルパーを使えば、以下のようにファイルアップロード用フィールドをレンダリングできます。

<%= form_with model: @person do |form| %>
  <%= form.file_field :csv_file %>
<% end %>

ファイルアップロードで忘れてはならない重要な点は、レンダリングされるフォームのenctype属性を必ずmultipart/form-dataに設定しておかなければならない点です。これは、以下のようにform_withの内側でfile_field_tagヘルパーを使えば自動で行われます。enctype属性は手動でも設定できます。

<%= form_with url: "/uploads", multipart: true do |form| %>
  <%= file_field_tag :csv_file %>
<% end %>

どちらの場合も、出力されるHTMLフォームは以下のようになります。

<form enctype="multipart/form-data" action="/people" accept-charset="UTF-8" method="post">
  <!-- ... -->
</form>

なお、form_withの規約によって、上述の2つのフィールド名が異なっている点にご注意ください。つまり前者のフォームではフィールド名がperson[csv_file]になり(params[:person][:csv_file]でアクセス可能)、後者のフォームでは単なるcsv_fileになります(params[:csv_file]でアクセス可能)。

6.1 CSVファイルのアップロード例

file_fieldを使う場合、paramsハッシュ内のオブジェクトはActionDispatch::Http::UploadedFileのインスタンスです。アップロードされたCSVファイルのデータをアプリケーションのレコードに保存する方法の例を次に示します。

  require "csv"

  def upload
    uploaded_file = params[:csv_file]
    if uploaded_file.present?
      csv_data = CSV.parse(uploaded_file.read, headers: true)
      csv_data.each do |row|
        # CSVファイルを1行ずつ処理する
        # SomeInvoiceModel.create(amount: row['Amount'], status: row['Status'])
        Rails.logger.info row.inspect
        #<CSV::Row "id":"po_1KE3FRDSYPMwkcNz9SFKuaYd" "Amount":"96.22" "Created (UTC)":"2022-01-04 02:59" "Arrival Date (UTC)":"2022-01-05 00:00" "Status":"paid">
      end
    end
    # ...
  end

ファイルをモデルと一緒に保存する必要がある画像(ユーザーのプロフィール写真など)である場合、ファイルの保存場所(ディスク、Amazon S3など)、画像ファイルのサイズ変更、サムネイルの生成など、考慮すべきタスクがいくつかあります。Active Storageは、このようなタスクを支援するように設計されています。

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

form_withfields_forによって生成されるオブジェクトは、フォームビルダーと呼ばれます。フォームビルダーはActionView::Helpers::FormBuilderのインスタンスであり、フォームビルダーを使うことで、モデル要素に関連付けられているフォーム要素を生成できます。このクラスは、アプリケーションにカスタムヘルパーを追加する形で拡張可能です。

たとえば、アプリケーション全体でtext_fieldlabelを表示する場合は、以下のヘルパーメソッドをapplication_helper.rbに追加できます。

module ApplicationHelper
  def text_field_with_label(form, attribute)
    form.label(attribute) + form.text_field(attribute)
  end
end

以下のように、このヘルパーを通常通りにフォーム内で利用します。

<%= form_with model: @person do |form| %>
  <%= text_field_with_label form, :first_name %>
<% end %>

ただし、ActionView::Helpers::FormBuilderのサブクラスを作成し、そこにヘルパーを追加することも可能です。このLabellingFormBuilderサブクラスを以下のように定義します。

class LabellingFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(attribute, options = {})
    # superは元のtext_fieldメソッドを呼び出す
    label(attribute) + super
  end
end

先ほどのフォームは以下で置き換え可能です。

<%= form_with model: @person, builder: LabellingFormBuilder do |form| %>
  <%= form.text_field :first_name %>
<% end %>

このクラスを頻繁に再利用する場合は、以下のようにlabeled_form_withヘルパーを定義してbuilder: LabellingFormBuilderオプションを自動的に適用してもよいでしょう。

module ApplicationHelper
  def labeled_form_with(**options, &block)
    options[:builder] = LabellingFormBuilder
    form_with(**options, &block)
  end
end

form_withの代わりに以下の書き方も可能です。

<%= labeled_form_with model: @person do |form| %>
  <%= form.text_field :first_name %>
<% end %>

上記の3つのケース(text_field_with_labelヘルパー、LabellingFormBuilderサブクラス、およびlabeled_form_withヘルパー)は、いずれも以下のように同じHTML出力を生成します。

<form action="/people" accept-charset="UTF-8" method="post">
  <!-- ... -->
  <label for="person_first_name">First name</label>
  <input type="text" name="person[first_name]" id="person_first_name">
</form>

ここで使われているフォームビルダーは、以下のコードが実行された時の動作も決定します。

<%= render partial: f %>

fActionView::Helpers::FormBuilderのインスタンスである場合、このコードはformパーシャルを生成し、そのパーシャルオブジェクトをフォームビルダーに設定します。このフォームビルダーのクラスがLabellingFormBuilderの場合、代わりにlabelling_formパーシャルがレンダリングされます。

LabellingFormBuilderなどのフォームビルダーのカスタマイズでは、実装の詳細が隠蔽されます(上記の単純な例ではやり過ぎのように思えるかもしれません)。フォームでカスタム要素をどの程度頻繁に利用するかに応じて、FormBuilderクラスを拡張するか、ヘルパーを作成するかなど、さまざまなカスタマイズから選択することになります。

8 フォーム入力の命名規約とparamsハッシュ

これまで説明したフォームヘルパーはすべて、ユーザーがさまざまなタイプの入力を行えるフォーム要素のHTMLを生成するのに役立ちます。ユーザー入力値にコントローラー側でアクセスするにはどうすればよいでしょうか。その答えがparamsハッシュです。paramsハッシュについては、上記の例で既に確認しました。このセクションでは、フォーム入力がparamsハッシュでどのように構造化されるかという命名規約について、より具体的に説明します。

paramsハッシュには、配列や、ハッシュの配列を含めることが可能です。値は、paramsハッシュの最上位レベルに置くことも、別のハッシュにネストすることも可能です。たとえば、Personモデルの標準のcreateアクションでは、params[:person]Personオブジェクトのすべての属性のハッシュになります。

HTMLフォーム自体にはユーザー入力データに対する固有の構造が定められておらず、生成されるのは名前と値の文字列のペアだけであることにご注意ください。アプリケーションで使われる配列やハッシュは、Railsで利用するパラメータ命名規約による結果です。

paramsハッシュ内のフィールドは、コントローラで許可しておく必要があります。

8.1 基本構造

ユーザー入力フォームデータの基本的な2つの構造は、配列とハッシュです。

ハッシュには、paramsの値にアクセスするための構文が反映されます。たとえば、フォームに次の内容が含まれている場合は以下のようになります。

<input id="person_name" name="person[name]" type="text" value="Henry"/>

このとき、paramsハッシュの内容は以下のようになります。

{ "person" => { "name" => "Henry" } }

コントローラ内でparams[:person][:name]でアクセスすると、送信された値を取り出せます。

ハッシュは、以下のように必要に応じて何階層でもネストできます。

<input id="person_address_city" name="person[address][city]" type="text" value="New York"/>

上のコードによってできるparamsハッシュは以下のようになります。

{ "person" => { "address" => { "city" => "New York" } } }

もう1つの構造は配列です。通常、Railsは重複するパラメータ名を無視しますが、パラメータ名が空の角かっこ[]で終わる場合、パラメータは配列に蓄積されます。

たとえば、ユーザーが複数の電話番号を入力できるようにするには、フォームに次のコードを配置します。

<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>

これにより、params[:person][:phone_number]は送信された電話番号の配列になります。

{ "person" => { "phone_number" => ["555-0123", "555-0124", "555-0125"] } }

8.2 配列とハッシュの組み合わせ

これら2つの概念は、組み合わせて使うことも可能です。ハッシュの要素の1つは、値が配列である可能性があります。前述の例では、params[:person]ハッシュには[:phone_number]というキーがあり、その値は配列です。

ハッシュの配列も利用できます。たとえば、フォームで以下のコード片を繰り返すことで、任意の個数の住所を作成できます。

<input name="person[addresses][][line1]" type="text"/>
<input name="person[addresses][][line2]" type="text"/>
<input name="person[addresses][][city]" type="text"/>
<input name="person[addresses][][line1]" type="text"/>
<input name="person[addresses][][line2]" type="text"/>
<input name="person[addresses][][city]" type="text"/>

これにより、ハッシュの配列params[:person][:addresses]が得られます。配列内の各ハッシュには、次のようなキーline1line2、およびcityが含まれます。

{ "person" =>
  { "addresses" => [
    { "line1" => "1000 Fifth Avenue",
      "line2" => "",
      "city" => "New York"
    },
    { "line1" => "Calle de Ruiz de Alarcón",
      "line2" => "",
      "city" => "Madrid"
    }
    ]
  }
}

ここで重要なのは、ハッシュはいくらでもネストできますが、配列は1階層しか使えない点に注意することです。配列はたいていの場合ハッシュで置き換えられます。たとえば、モデルオブジェクトの配列の代わりに、モデルオブジェクトのハッシュを使えます。このキーではid、配列インデックスなどのパラメータが利用できます。

配列パラメータは、checkboxヘルパーとの相性がよくありません。HTMLの仕様では、オンになっていないチェックボックスからは値が送信されません。しかし、チェックボックスからは常に値が送信される方が何かと便利です。そこでcheckboxヘルパーでは、同じ名前で予備の隠し入力を作成しておき、本来送信されないはずのチェックボックス値が見かけ上送信されるようになっています。チェックボックスがオフになっていると隠し入力値だけが送信され、チェックボックスがオンになっていると本来のチェックボックス値と隠し入力値が両方送信されますが、このとき優先されるのは本来のチェックボックス値の方です。この隠しフィールドを省略したい場合は、include_hiddenオプションをfalseに設定できます(デフォルトではtrue)。

8.3 添字付きのハッシュ

たとえば、各個人の住所に対応するフィールドのセットを持つフォームをレンダリングしたいとします。こんなときはfields_forヘルパーと、ハッシュの添字を指定する:indexオプションが便利です。

<%= form_with model: @person do |person_form| %>
  <%= person_form.text_field :name %>
  <% @person.addresses.each do |address| %>
    <%= person_form.fields_for address, index: address.id do |address_form| %>
      <%= address_form.text_field :city %>
    <% end %>
  <% end %>
<% end %>

この個人が2つの住所を持っていて、idがそれぞれ23と45だとすると、上のフォームから以下のようなHTMLが出力されます。

<form accept-charset="UTF-8" action="/people/1" method="post">
  <input name="_method" type="hidden" value="patch" />
  <input id="person_name" name="person[name]" type="text" />
  <input id="person_address_23_city" name="person[address][23][city]" type="text" />
  <input id="person_address_45_city" name="person[address][45][city]" type="text" />
</form>

このときのparamsハッシュは以下のようになります。

{
  "person" => {
    "name" => "Bob",
    "address" => {
      "23" => {
        "city" => "Paris"
      },
      "45" => {
        "city" => "London"
      }
    }
  }
}

フォームビルダーのperson_formfields_forを呼び出したので、フォームのすべてのinputは"person"ハッシュに対応付けられます。また、index: address.idを指定することで、各都市のinputのname属性をperson[address][city]ではなくperson[address][#{address.id}][city]としてレンダリングしています。このように、paramsハッシュを処理する際に、どのAddressレコードを変更すべきかを決定可能になります。

fields_forで添字を利用するindexオプションについて詳しくは、APIドキュメントを参照してください。

9 複雑なフォームを作成する

アプリケーションが大きくなるにつれて、単一オブジェクトの編集を超える複雑なフォームを作成しなければならなくなる場合があります。たとえば、Personを作成するときに、ユーザーが同じフォーム内に複数のAddressレコード(自宅、職場など)を作成できます。後でユーザーがPersonレコードを編集するときに、住所を追加・削除・更新できるようにしておく必要もあります。

9.1 モデルをネステッド属性用に構成する

特定のモデル(この場合はPerson)に関連付けられているレコードを編集するために、Active Recordはaccepts_nested_attributes_forメソッドを介したモデルレベルのサポートを提供します。

class Person < ApplicationRecord
  has_many :addresses, inverse_of: :person
  accepts_nested_attributes_for :addresses
end

class Address < ApplicationRecord
  belongs_to :person
end

上のコードによってaddresses_attributes=メソッドがPersonモデル上に作成され、これを用いて住所の作成、更新、および削除を行なえます。

9.2 ネストしたフォームをビューに追加する

ユーザーは以下のフォームを用いてPersonとそれに関連する複数の住所を作成できます。

<%= form_with model: @person do |form| %>
  Addresses:
  <ul>
    <%= form.fields_for :addresses do |addresses_form| %>
      <li>
        <%= addresses_form.label :kind %>
        <%= addresses_form.text_field :kind %>

        <%= addresses_form.label :street %>
        <%= addresses_form.text_field :street %>
        ...
      </li>
    <% end %>
  </ul>
<% end %>

関連付けにネステッド属性が渡されると、fields_forヘルパーは関連付けの要素ごとにブロックを1回ずつレンダリングします。特に、Personに住所が登録されていない場合は何もレンダリングされません。

フィールドのセットが1個以上ユーザーに表示されるように、コントローラで1つ以上の空白の子要素を作成しておくというのはよく行われるパターンです。以下の例では、新しいPersonフォームに2組みの住所フィールドがレンダリングされます。

たとえば、上記のform_withに以下の変更を加えたとします。

def new
  @person = Person.new
  2.times { @person.addresses.build }
end

上のコードによって以下のHTMLが出力されます。

<form action="/people" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="lWTbg-4_5i4rNe6ygRFowjDfTj7uf-6UPFQnsL7H9U9Fe2GGUho5PuOxfcohgm2Z-By3veuXwcwDIl-MLdwFRg" autocomplete="off">
  Addresses:
  <ul>
      <li>
        <label for="person_addresses_attributes_0_kind">Kind</label>
        <input type="text" name="person[addresses_attributes][0][kind]" id="person_addresses_attributes_0_kind">

        <label for="person_addresses_attributes_0_street">Street</label>
        <input type="text" name="person[addresses_attributes][0][street]" id="person_addresses_attributes_0_street">
        ...
      </li>

      <li>
        <label for="person_addresses_attributes_1_kind">Kind</label>
        <input type="text" name="person[addresses_attributes][1][kind]" id="person_addresses_attributes_1_kind">

        <label for="person_addresses_attributes_1_street">Street</label>
        <input type="text" name="person[addresses_attributes][1][street]" id="person_addresses_attributes_1_street">
        ...
      </li>
  </ul>
</form>

fields_forヘルパーはフォームフィールドを1つ生成します。accepts_nested_attributes_forヘルパーが受け取るのはこのようなパラメータの名前です。たとえば、2つの住所を持つユーザーを1人作成する場合、送信されるparams内のパラメータは以下のようになります。

{
  "person" => {
    "name" => "John Doe",
    "addresses_attributes" => {
      "0" => {
        "kind" => "Home",
        "street" => "221b Baker Street"
      },
      "1" => {
        "kind" => "Office",
        "street" => "31 Spooner Street"
      }
    }
  }
}

この:address_attributesハッシュのキーの実際の値は重要ではありませんが、アドレスごとに異なる整数の文字列である必要があります。

関連付けられたオブジェクトが既に保存されている場合、fields_forメソッドは、保存されたレコードのidを持つ隠し入力を自動生成します。fields_forinclude_id: falseを渡すことでこの自動生成をオフにできます。

{
  "person" => {
    "name" => "John Doe",
    "addresses_attributes" => {
      "0" => {
        "id" => 1,
        "kind" => "Home",
        "street" => "221b Baker Street"
      },
      "1" => {
        "id" => "2",
        "kind" => "Office",
        "street" => "31 Spooner Street"
      }
    }
  }
}

9.3 コントローラでパラメータを許可する

コントローラ内でパラメータをモデルに渡す前に、いつもと同様にコントローラ内でパラメータの許可リストチェックを宣言する必要があります。

def create
  @person = Person.new(person_params)
  # ...
end

private
  def person_params
    params.expect(person: [ :name, addresses_attributes: [[ :id, :kind, :street ]] ])
  end

9.4 関連付けられているオブジェクトを削除する

accepts_nested_attributes_forallow_destroy: trueを渡すと、関連付けられているオブジェクトをユーザーが削除することを許可できます。

class Person < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses, allow_destroy: true
end

あるオブジェクトの属性のハッシュに、キーが_destroyで、値がtrueと評価可能(1'1'true'true'など)な組み合わせがあると、そのオブジェクトは破棄されます。以下のフォームではユーザーが住所を削除可能です。

<%= form_with model: @person do |form| %>
  Addresses:
  <ul>
    <%= form.fields_for :addresses do |addresses_form| %>
      <li>
        <%= addresses_form.checkbox :_destroy %>
        <%= addresses_form.label :kind %>
        <%= addresses_form.text_field :kind %>
        ...
      </li>
    <% end %>
  </ul>
<% end %>

_destroyフィールドのHTMLは以下のようになります。

<input type="checkbox" value="1" name="person[addresses_attributes][0][_destroy]" id="person_addresses_attributes_0__destroy">

このとき、コントローラ内でパラメータの許可リストを以下のように更新して、_destroyフィールドが必ずパラメータに含まれるようにしておく必要もあります。

def person_params
  params.require(:person).
    permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy])
end

9.5 空のレコードができないようにする

ユーザーが何も入力しなかったフィールドを無視できれば何かと便利です。これは、:reject_if procをaccepts_nested_attributes_forに渡すことで制御できます。このprocは、フォームから送信された属性にあるハッシュごとに呼び出されます。このprocがtrueを返す場合、Active Recordはそのハッシュに関連付けられたオブジェクトを作成しません。以下の例では、kind属性が設定されている場合にのみ住所オブジェクトを生成します。

class Person < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses, reject_if: lambda { |attributes| attributes["kind"].blank? }
end

代わりにシンボル:all_blankを渡すこともできます。このシンボルが渡されると、_destroyの値を除くすべての属性が空白レコードを受け付けなくなるprocが1つ生成されます。

10 外部リソース用のフォーム

外部リソースに何らかのデータを渡す必要がある場合も、Railsのフォームヘルパーを用いてフォームを作成する方がやはり便利です。外部APIに対してauthenticity_tokenを設定することが期待されている場合は、以下のようにform_withauthenticity_token: '外部トークン'パラメータを渡すことで実現できます。

<%= form_with url: "http://farfar.away/form", authenticity_token: "external_token" do %>
  Form contents
<% end %>

場合によっては、フォームで利用可能なフィールドが外部APIによって制限されていて、authenticity_token隠しフィールドを生成すると不都合が生じることがあります。トークンを送信しないようにするには、以下のように:authenticity_tokenオプションにfalseを渡します。

<%= form_with url: "http://farfar.away/form", authenticity_token: false do %>
  Form contents
<% end %>

11 フォームビルダーなしで利用できるタグヘルパー

フォームのフィールドをフォームビルダーのコンテキストの外でレンダリングする必要が生じたときのために、よく使われるフォーム要素を生成するタグヘルパーを提供しています。たとえば、checkbox_tagは以下のように使えます。

<%= checkbox_tag "accept" %>

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

<input type="checkbox" name="accept" id="accept" value="1" />

一般に、これらのヘルパー名は、フォームビルダーのヘルパー名の末尾に_tagを追加したものになります。完全なリストについては、FormTagHelper APIドキュメントを参照してください。

12 form_tagform_forの利用について

Rails 5.1でform_withが導入されるまでは、form_withの機能はform_tagform_forに分かれていました。form_tagおよびform_forは、禁止ではないものの、利用は推奨されていません。現在はform_withの利用が推奨されています。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加