Action View フォームヘルパー

Webアプリケーションにおけるフォームは、ユーザー入力を扱うのに不可欠なインターフェイスです。しかしフォームのコントロールの命名法は今ひとつで、しかも多数の属性があるため、フォームのマークアップは作成も保守も退屈な作業になりがちです。そこでRailsでは、フォームのマークアップを生成するビューヘルパーを提供し、これらの煩雑な作業を行わないで済むようにしました。しかしながら現実のユースケースはさまざまであるため、開発者はこれらを実際に使用する前に、これらのよく似たヘルパーメソッド群にどのような違いがあるのかをすべて把握しておく必要があります。

このガイドの内容:

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

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

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

<%= form_tag do %>
  Form contents
<% end %>

上のように引数なしで呼び出されると<form>タグを生成します。このフォームを現在のページに送信するときにはHTTPのPOSTメソッドが使用されます。たとえば現在のページが/home/indexの場合、以下のようなHTMLが生成されます (読みやすくするため改行を追加してあります)。

<form accept-charset="UTF-8" action="/home/index" method="post">
  <div style="margin:0;padding:0">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
  </div>
  Form contents
</form>

上のフォームに何か余分なものがあることにお気付きでしょうか。divタグに囲まれた中に、2つの隠しinput要素が置かれています。このdivタグは省略できません。これがないとフォームを正常に送信できないのです。最初のutf8隠しinput要素では、フォームの文字エンコーディングを指定のとおりにブラウザに強制します。これはアクションが"GET"と"POST"のどちらであってもすべてのフォームで生成されます。2番目隠しinput要素であるauthenticity_token要素は クロスサイトリクエストフォージェリへの保護 のためのセキュリティ機能です。この要素はGET以外のすべてのフォームで生成されます (セキュリティ機能が有効になっている場合)。詳細についてはセキュリティガイドを参照してください。

本ガイドでは以後、隠しinput要素の例を簡潔にするためdivを省略します。

1.1 一般的な検索フォーム

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

  • "GET"メソッドを対象としたフォーム要素
  • 入力するものを示すラベル
  • テキスト入力要素
  • [送信]ボタン要素

このフォームを作成するには、form_taglabel_tagtext_field_tagsubmit_tagが必要です。以下に例を示します。

<%= form_tag("/search", method: "get") do %>
  <%= label_tag(:q, "Search for:") %>
  <%= text_field_tag(:q) %>
  <%= submit_tag("Search") %>
<% end %>

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

<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
  <label for="q">Search for:</label>
  <input id="q" name="q" type="text" />
  <input name="commit" type="submit" value="Search" />
</form>

どのフォームinputを使用した場合でも、id属性はその名前から生成されます (上の例では「q」)。これらのidは、cssでのスタイル追加やJavaScriptによるフォーム制御で使用するのに便利です。

HTMLの すべての フォームコントロールには、text_field_tagsubmit_tagと同様の便利なヘルパーが用意されています。

フォームを検索に使用する場合は必ず"GET"メソッドを使用してください。こうすることで、検索クエリがURLの一部となるので、ユーザーが検索結果をブックマークすると同じ検索を後でブックマークから実行することができます。Railsでは基本的に、アクションに対応する適切なHTTP verbを常に選ぶようにしてください (訳注: セキュリティガイドにも記載されていますが、更新フォームでGETメソッドを使用すると重大なセキュリティホールが生じます)。

1.2 フォームヘルパーの呼び出しで複数のハッシュを使用する

form_tagヘルパーは2つの引数を取ります。1つはアクションへのパスで、もう1つはオプションのハッシュです。このハッシュには、フォーム送信のメソッドと、HTMLオプション(フォーム要素のクラスなど)が含まれます。

link_toヘルパーのときと同様、文字列以外の引数も受け取れます。たとえば、Railsのルーティングメカニズムで認識可能なURLパラメータのハッシュを受け取り、このハッシュを正しいURLに変換することができます。ただし、form_tagの引数を両方ともハッシュにするとたちまち問題が生じるでしょう。たとえば次のようなコードを書いたとします。

form_tag(controller: "people", action: "search", method: "get", class: "nifty_form")
# => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'

上のコードでは、生成されたURLにmethodclassが追加されてしまっています。たとえ2つのハッシュを書いたつもりでも、実際にはそれらが1つのものとして扱われてしまっています。従って、波かっこ { } を使用して1つ目のハッシュを (あるいはどちらのハッシュも) 区別してあげる必要があります。今度は期待どおりのHTMLが生成されます。

form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form")
# => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">'

1.3 フォーム要素生成に使用するヘルパー

Railsには、チェックボックス/テキストフィールド/ラジオボタンなどのフォーム要素を生成するためのヘルパーが多数用意されています。これらの基本的なヘルパーは名前が_tagで終わっており (text_field_tagcheck_box_tagなど)、それぞれただ1つの<input>要素を生成します。これらのヘルパーの1番目のパラメータは、inputの名前と決まっています。フォームが送信されると、この名前がフォームデータに含まれて渡され、ユーザーが入力した値とともに、コントローラ内でparamsとなってアクセス可能になります。たとえば、フォームに<%= text_field_tag(:query) %>というコードが含まれていたとすると、コントローラでparams[:query]と指定することによってこのフィールドの値にアクセスできます。

Railsは、inputに名前を与えるときに一定のルールに従っています。これにより、配列やハッシュのような「非スカラー値」のパラメータをフォームから送信できるようになり、その結果paramsとしてコントローラでアクセスできるようになるのです。詳細については本ガイドの7章を参照してください。これらのヘルパーの正確な使用法についてはAPIドキュメントを参照してください。

1.3.1 チェックボックス

チェックボックスはフォームコントロールの一種で、ユーザーがオプションをオンまたはオフにできるようにするためのものです。

<%= check_box_tag(:pet_dog) %>
<%= label_tag(:pet_dog, "I own a dog") %>
<%= check_box_tag(:pet_cat) %>
<%= label_tag(:pet_cat, "I own a cat") %>

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

<input id="pet_dog" name="pet_dog" type="checkbox" value="1" />
<label for="pet_dog">I own a dog</label>
<input id="pet_cat" name="pet_cat" type="checkbox" value="1" />
<label for="pet_cat">I own a cat</label>

check_box_tagの最初のパラメータは、言うまでもなくinputの名前です。2番目のパラメータは、input(タグ)のvalue属性になります。チェックボックスをオンにすると、この値はフォームデータに含まれ、最終的にparamsに渡されます。

1.3.2 ラジオボタン

ラジオボタンも、チェックボックスと同様に一連のオプションをユーザーが選択できるようにするものですが、一度に1つの項目しか選択できない排他的な動作が特徴です。

<%= radio_button_tag(:age, "child") %>
<%= label_tag(:age_child, "I am younger than 21") %>
<%= radio_button_tag(:age, "adult") %>
<%= label_tag(:age_adult, "I'm over 21") %>

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

<input id="age_child" name="age" type="radio" value="child" />
<label for="age_child">I am younger than 21</label>
<input id="age_adult" name="age" type="radio" value="adult" />
<label for="age_adult">I'm over 21</label>

check_box_tagヘルパーのときと同様、radio_button_tagの2番目のパラメータがinput(タグ)のvalue属性になります。2つのラジオボタン項目は同じ名前 ('age') を共有しているので、ユーザーはどちらかの値だけを選択できます。そしてparams[:age]の値は"child"と"adult"のどちらかになります。

チェックボックスとラジオボタンには必ずラベルを表示してください。ラベルを表示することで、そのオプションとラベルの名前が関連付けられるだけでなく、ラベルの部分までクリック可能になるのでユーザーにとってクリックしやすくなります。

1.4 その他のヘルパー

これまで紹介した他にも、次のようなフィールドがあります: テキストエリア、パスワード、隠しフィールド、検索フィールド、電話番号フィールド、日付フィールド、時刻フィールド、色フィールド、ローカル日時フィールド、月フィールド、週フィールド、URLフィールド、メールアドレスフィールド、数値フィールド、範囲フィールド。

<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
<%= password_field_tag(:password) %>
<%= hidden_field_tag(:parent_id, "5") %>
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
<%= range_field(:product, :discount, in: 1..100) %>

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

<textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea>
<input id="password" name="password" type="password" />
<input id="parent_id" name="parent_id" type="hidden" value="5" />
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
<input id="user_birthday_month" name="user[birthday_month]" type="month" />
<input id="user_birthday_week" name="user[birthday_week]" type="week" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
<input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" />
<input id="product_discount" max="100" min="1" name="product[discount]" type="range" />

隠しフィールドはユーザーには表示されず、事前に与えられた値を種類を問わず保持します。隠しフィールドに含まれている値はJavaScriptを使用して変更できます。

「検索、電話、日付、時刻、色、日時、ローカル日時、月、週、URL、メールアドレス、数値、範囲」フィールドはHTML5から利用できるようになったコントロールです。 これらのフィールドを古いブラウザでも同じように扱いたいのであれば、CSSやJavaScriptを使用したHTML5ポリフィルが必要になるでしょう。 古いブラウザでHTML5に対応する方法は山ほどありますが、現時点で代表的なものはModernizrでしょう。これらは、HTML5の新機能が使用されていることが検出された場合に、機能を追加するためのシンプルな方法を提供します。

パスワード入力フィールドを使用しているのであれば、入力されたパスワードをRailsのログに残さないようにしたいと思うことでしょう。その方法についてはセキュリティガイドを参照してください。

2 モデルオブジェクトの取り扱い

2.1 モデルオブジェクトヘルパー

フォームの主な仕事といえば、モデルオブジェクトの作成および修正でしょう。*_tagヘルパーをモデルオブジェクトの作成/修正に用いることはもちろん可能ですが、1つ1つのタグについて正しいパラメータが使用されているか、入力のデフォルト値は適切に設定されているかなどをいちいちコーディングするのは何とも面倒です。Railsにはまさにこのような作業を軽減するのにうってつけのヘルパーがあります。なお、これらのヘルパー名には_tagが付いていません (text_fieldtext_areaなど)

これらのヘルパーの最初の引数はインスタンス変数名、2番目の引数はオブジェクトを呼び出すためのメソッド名 (通常は属性名を使用します)です。Railsは、オブジェクトのそのメソッドから値が返され、かつ適切な入力名が設定されるように、入力コントロールの値を設定してくれます。たとえば、コントローラで@personが定義されており、その人物の名前がHenryだとします。

<%= text_field(:person, :name) %>

このとき、上のコードからは以下の出力が得られます。

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

このフォームを送信すると、ユーザーが入力した値はparams[:person][:name]に保存されます。params[:person]ハッシュはPerson.newに渡しやすくなっています。@personがPersonモデルのインスタンスであれば@person.updateにも渡しやすくなっています。これらのヘルパーでは2番目のパラメータとして属性名を渡すことがほとんどですが、必ずしもそうでないヘルパーもあります。上の例で言うなら、personオブジェクトにnameメソッドとname=メソッドがありさえすればRailsは余分な作業をせずに済みます。

ヘルパーに渡すのはインスタンス変数の「名前」でなければなりません (シンボル:personや文字列"person"など)。渡すのはモデルオブジェクトのインスタンスそのものではありません。

Railsのヘルパーには、モデルオブジェクトに関連する検証 (バリデーション) エラーを自動的に表示する機能もあります。詳細については本ガイドのActive Record検証 (バリデーション)を参照してください。

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

上のやり方でだいぶコーディングが楽になりましたが、改善の余地はまだまだあります。Personモデルに多数の属性があると、編集されたオブジェクトの名前を何度も繰り返さなければなりません。もっと楽に、フォームとモデルオブジェクトを結び付けるだけで簡単に作れないものか。それがまさにform_forなのです。

記事を扱うArticlesコントローラapp/controllers/articles_controller.rbがあるとします。

def new
  @article = Article.new
end

上のコントローラに対応するビューapp/views/articles/new.html.erbform_forを使うと、以下のような感じになります。

<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body, size: "60x12" %>
  <%= f.submit "Create" %>
<% end %>

以下の点にご注目ください。

  • @articleは、実際に編集されるオブジェクトそのものです (名前ではありません)。
  • 1つのオプションに1つのハッシュが使用されています。ルーティングオプションが:urlハッシュで渡され、HTMLオプションが:htmlハッシュで渡されています。フォームで:namespaceオプションを使用して、フォーム要素上のid属性同士が衝突しないようにすることもできます。この名前空間属性の値は、生成されたHTMLのid属性の先頭にアンダースコア付きで追加されます。
  • form_forメソッドからは フォームビルダー オブジェクト(ここでは変数f)が生成されます。
  • フォームコントロールを作成するメソッドは、 フォームビルダーオブジェクトfに対して 呼び出されます。

これにより、以下のHTMLが生成されます。

<form class="nifty_form" id="new_article" action="/articles" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="NRkFyRWxdYNfUg7vYxLOp2SLf93lvnl+QwDWorR42Dp6yZXPhHEb6arhDOIWcqGit8jfnrPwL781/xlrzj63TA==" />
  <input type="text" name="article[title]" id="article_title" />
  <textarea name="article[body]" id="article_body" cols="60" rows="12"></textarea>
  <input type="submit" name="commit" value="Create" data-disable-with="Create" />
</form>

form_forに渡される名前は、paramsを使用してフォームの値にアクセスするときのキーに影響します。たとえば、この名前がarticleだとすると、すべての入力はarticle[属性名]というフォーム名を持ちます。従ってcreateアクションでは、:titleキーと:bodyキーを持つ1つのハッシュがparams[:article]に含まれることになります。input名の重要性については、パラメータの命名ルールを理解するを参照してください。

フォームビルダー変数に対して呼び出されるヘルパーメソッドは、モデルオブジェクトのヘルパーメソッドと同一です。ただし、フォームの場合は編集の対象となるオブジェクトが既にフォームビルダーで管理されているので、どのオブジェクトに対して編集を行うかを指定する必要がない点が異なります。

fields_forメソッドを使用すれば、<form>タグを実際に作成することなく同様の結び付きを設定することができます。これは、同じフォームで別のモデルオブジェクトも編集できるようにしたい場合などに便利です。たとえば、Personモデルに関連付けられているContactDetailモデルがあるとすると、以下のようなフォームを作成すればよいのです。

<%= form_for @person, url: {action: "create"} do |person_form| %>
  <%= person_form.text_field :name %>
  <%= fields_for @person.contact_detail do |contact_detail_form| %>
    <%= contact_detail_form.text_field :phone_number %>
  <% end %>
<% end %>

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

<form class="new_person" id="new_person" action="/people" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="bL13x72pldyDD8bgtkjKQakJCpd4A8JdXGbfksxBDHdf1uC0kCMqe2tvVdUYfidJt0fj3ihC4NxiVHv8GVYxJA==" />
  <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_forで生成されたものと似ています(実はform_forの内部ではfields_forが呼び出されています)。

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

これでArticleモデルをユーザーが直接操作できるようになりました。Rails開発で次に行なうべき最善の方法は、これを リソース として宣言することです。

resources :articles

リソースを宣言すると、自動的に他にも多くの設定が行われます。リソースの設定方法の詳細については、Railsルーティングガイドを参照してください。

RESTfulなリソースを扱っている場合、レコード識別(record identification)を使用するとform_forの呼び出しがはるかに簡単になります。これは、モデルのインスタンスを渡すだけで、後はRailsがそこからモデル名など必要なものを取り出して処理してくれるというものです。

## 新しい記事の作成
# 長いバージョン
form_for(@article, url: articles_path)
# 短いバージョン(レコード識別を利用)
form_for(@article)

## 既存の記事の修正
# 長いバージョン
form_for(@article, url: article_path(@article), html: {method: "patch"})
# 短いバージョン
form_for(@article)

この短いform_for呼び出しは、レコードの作成・編集のどちらにおいてもまったく同じになっています。これがどれほど便利であるかおわかりいただけると思います。レコード識別は、レコードが新しい場合にはrecord.new_record?が必要とされている、などの適切な推測を行ってくれます。さらに送信用の正しいパスを選択し、オブジェクトのクラスに基づいた名前も選択してくれます。

Railsはフォームのclassidを自動的に設定してくれます。この場合、記事を作成するフォームにはidと、new_articleというclassが与えられます。もし仮にidが23の記事を編集しようとしているのであれば、classedit_articleに設定され、idはedit_article_23に設定されます。なお、煩雑さを避けるため、以後これらの属性の表記は割愛します。

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

2.3.1 名前空間を扱う

名前空間付きのルーティングを作成してある場合、form_forでもこれを利用した簡潔な表記が利用できます。アプリケーションのルーティングでadmin名前空間が設定されているとします。

form_for [:admin, @article]

上のコードはそれによって、admin名前空間内にあるArticlesControllerに送信を行なうフォームを作成します (たとえば更新の場合はadmin_article_path(@article)に送信されます)。名前空間が多段階層になっている場合にも同様の文法が使用できます。

form_for [:admin, :management, @article]

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

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

Railsのフレームワークは、開発者がアプリケーションをRESTfulなデザインで構築するように働きかけています。すなわち、開発者はGETやPOSTリクエストだけでなく、PATCHやDELETEリクエストをたくさん作成・送信することになります。しかしながら、現実には多くのブラウザはフォーム送信時にGETとPOST以外のHTTPメソッドを サポートしていません

そこでRailsでは、POSTメソッド上でこれらのメソッドをエミュレートすることによってこの問題を解決しています。具体的には、"_method"という名前の隠し入力をフォームに用意し、使いたいメソッドをここで指定します。

form_tag(search_path, method: "patch")

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

<form accept-charset="UTF-8" action="/search" method="post">
  <div style="margin:0;padding:0">
    <input name="_method" type="hidden" value="patch" />
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
  </div>
  ...

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

3 セレクトボックスを簡単に作成する

HTMLでセレクトボックスを作成するには大量のマークアップを書かなくてはなりません(選択する1つのオプションに1つのOPTION要素が対応します)。従って、このようなマークアップを自動的に生成できるようにしたいと考えるのは自然な流れです。

HTMLマークアップは通常であれば以下のような感じになります。

<select name="city_id" id="city_id">
  <option value="1">Lisbon</option>
  <option value="2">Madrid</option>
  ...
  <option value="12">Berlin</option>
</select>

ここでは都市の名前が一覧としてユーザーに示されています。アプリケーションの内部では、これらの項目のidを扱えればそれでよいのです。それによってそれらのidがオプションの値属性として使用できるようになります。Railsの内部でどのようなことが行われているかを見てみましょう。

3.1 SelectタグとOptionタグ

最も一般的なヘルパーはselect_tagでしょう。これはその名の通り、オプションの文字列を内包したSELECTタグを生成するだけのメソッドです。

<%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %>

まずは上のコードを書きますが、これだけではオプションタグは動的生成されません。オプションタグを生成するにはoptions_for_selectヘルパーを使用します。

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %>

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

<option value="1">Lisbon</option>
<option value="2">Madrid</option>
...

options_for_selectの最初の引数は入れ子になった配列であり、各要素には「オプションテキスト(city name)」と「オプション値(city id)」があります。オプション値の部分がコントローラに送信されます。送信されるidは、対応するデータベースオブジェクトのidであるのが普通ですが、必ずしもそうする必要はありません。

ここを理解すれば、select_tagoptions_for_selectを組み合わせて望み通りの完全なマークアップを得ることができます。

<%= select_tag(:city_id, options_for_select(...)) %>

options_for_selectでは、デフォルトにしたいオプションを値を渡すことでデフォルト値を設定できます。

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %>

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

<option value="1">Lisbon</option>
<option value="2" selected="selected">Madrid</option>
...

生成されるオプション内部の値がこの値とマッチすると、Railsはselected属性を自動的にそのオプションに追加します。

:include_blank:promptが指定されていなくても、選択属性requiredがtrueになっていると、:include_blankは強制的にtrueに設定され、表示のsize1になり、multipleはtrueになりません。

ハッシュを使用して任意の値を追加することができます。

<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %>

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

<option value="1" data-size="2.8 million">Lisbon</option>
<option value="2" selected="selected" data-size="3.2 million">Madrid</option>
...

3.2 モデルを扱うセレクトボックス

ほとんどの場合、フォームコントロールは特定のデータベースと結び付けられるものであり、Railsがそのためのヘルパーを提供してくれることを期待するのは当然です。他のフォームヘルパーのときと同じ要領で、モデルを扱う場合にはselect_tagから_tagという接尾語を取り除きます。

# controller:
@person = Person.new(city_id: 2)
# view:
<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %>

第3のパラメータであるオプション配列は、options_for_selectに渡した引数と同じ種類のものです。このヘルパーのメリットの1つは、ユーザーが既に街を選んでいる場合に、正しい街がデフォルトの値として事前に選択されているかどうかを気にする必要がないという点です。Railsは@person.city_id属性を読み出してこれらを肩代わりしてくれます。

他のヘルパーのときと同様、@personオブジェクトを対象としたフォームビルダーでselectヘルパーを使用するのであれば、以下のような文法になります。

# フォームビルダーに対して選択を行なう
<%= f.select(:city_id, ...) %>

selectヘルパーにブロックを渡すこともできます。

<%= f.select(:city_id) do %>
  <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%>
    <%= content_tag(:option, c.first, value: c.last) %>
  <% end %>
<% end %>

selectヘルパー(および類似のcollection_selectヘルパー、select_tagヘルパーなど)を使用してbelongs_to関連付けを設定する場合は、関連付けそのものの名前ではなく、外部キーの名前(上の例であればcity_id)を渡す必要があります。city_idではなくcityを渡すと、Person.newまたはPerson.updateparamsハッシュを渡した時にActive RecordでActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)エラーが発生します。さらに、属性の編集のみを行なうフォームヘルパーについても注意が必要です。ユーザーが外部キーを直接操作できてしまうとセキュリティ上の問題が生じる可能性があるため、十分注意してください。

3.3 任意のオブジェクトのコレクションに対してオプションタグを使用する

options_for_selectを使用してオプションタグを生成する場合、各オプションのテキストと値を含む配列が作成されている必要があります。ここでCityモデルというものがあるとして、それらのオブジェクトのコレクションからオプションタグを生成するにはどうしたらよいでしょうか。ひとつの方法は、コレクションをイテレートしてネストした配列を作成することです。

<% cities_array = City.all.map { |city| [city.name, city.id] } %>
<%= options_for_select(cities_array) %>

これはこれでまったく正当な方法ですが、Railsにはもっと簡潔なoptions_from_collection_for_selectヘルパーがあります。このヘルパーは、任意のオブジェクトのコレクションの他に2つの引数 ( value オプションと text オプションをそれぞれ読み出すためのメソッド名) を取ります。

<%= options_from_collection_for_select(City.all, :id, :name) %>

その名前が示すとおり、このヘルパーが生成するのはオプションタグだけです。実際に動作するセレクトボックスを生成するには、このメソッドをoptions_for_selectと併用したときと同様、このメソッドとselect_tagを併用する必要があります。モデルオブジェクトを使用して作業する場合、selectselect_tagおよびoptions_for_selectと組み合わせた場合と同様、collection_selectselect_tagおよびoptions_from_collection_for_selectと組み合わせます。

<%= collection_select(:person, :city_id, City.all, :id, :name) %>

要約すると、options_from_collection_for_selectヘルパーは「options_for_selectselectするもの」を「collection_selectする」ということです。

options_for_selectに渡されるペアでは、名前が1番目でidが2番目でしたが、options_from_collection_for_selectの場合は1番目の引数はvalueメソッドで2番目の引数はtextメソッドです。

3.4 タイムゾーンと国を選択する

Railsでタイムゾーンをサポートするために、ユーザーが今どのタイムゾーンにいるのかを何らかの形でユーザーに尋ねなければなりません。そのためには、collection_selectヘルパーを使用して、事前定義済みのTimeZoneオブジェクトのリストからセレクトボックスを作成する必要がありますが、実はこの機能を実現するtime_zone_selectというそれ専用のヘルパーが既に用意されています。

<%= time_zone_select(:person, :time_zone) %>

time_zone_options_for_selectという類似のヘルパーもあり、こちらではより細かい設定を行なうことができます。これら2つのメソッドに渡せる引数の詳細については、APIドキュメントを参照してください。

以前のRailsでは、country_selectヘルパーを使用して国を選択して いました が、この機能はcountry_selectプラグインに書き出されました。この機能を使用する場合、どの国名をリストに含め、どの国を含めないかを決める際に政治的な議論に関わらざるをえない点に留意してください(この機能がプラグイン化された理由も実はそれです)。

4 日付時刻フォームヘルパーを使用する

HTML5標準の日付/時刻入力フィールドを生成するヘルパーの代りに別の日付/時刻ヘルパーを使用することもできます。いずれにしろ、日付/時刻ヘルパーは以下の2つの点が他のヘルパーと異なっています。

  • 日付と時刻を一度に表す入力要素はありません。そのため、年、月、日などの個別のコンポーネントをいくつも使用しなければならず、従ってparamsハッシュ内でも日付時刻は単一の値では表されません。
  • 他のヘルパーでは、そのヘルパーが最小限の基本機能を持つ (ベアボーン) ものであるか、あるいはモデルオブジェクトを扱うものであるかを_tag接尾語の有無で表します。日付/時刻ヘルパーの場合は、select_dateselect_timeselect_datetimeがベアボーンヘルパーで、date_selecttime_selectdatetime_selectがモデルオブジェクトヘルパーに相当します。

どちらのヘルパーファミリーを使用しても、年・月・日などさまざまなコンポーネントのセレクトボックスを同じように作成できます。

4.1 ベアボーンヘルパー

select_*で始まる日付/時刻ヘルパーファミリーでは、Date、Time、DateTimeのいずれかのインスタンスを1番目の引数に取り、現在選択中の値として使用されます。現在の日付が使用される場合はこのパラメータを省略できます。例:

<%= select_date Date.today, prefix: :start_date %>

上のコードから以下の出力が得られます(煩雑さを避けるため実際のオプション値を省略しています)。

<select id="start_date_year" name="start_date[year]"> ... </select>
<select id="start_date_month" name="start_date[month]"> ... </select>
<select id="start_date_day" name="start_date[day]"> ... </select>

上の入力の結果はparams[:start_date]に反映され、キーは:year:month:dayとなります。これらの値から実際のTimeオブジェクトやDateオブジェクトを得るには、値を取り出して適切なコンストラクタに渡す必要があります。

Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)

:prefixオプションは、paramsハッシュから日付コンポーネントのハッシュを取り出すのに使用されるキーです。これでstart_dateに設定されました。省略するとdateに設定されます。

4.2 モデルオブジェクトヘルパー

select_dateヘルパーはActive Recordオブジェクトの更新・作成を行なうフォームでは扱いにくくなっています。Active Recordは、paramハッシュに含まれる要素がそれぞれ1つの属性にのみ対応していることを前提としているからです。 日付/時刻用のモデルオブジェクトヘルパーは、特殊な名前を持つパラメータを送信します。Active Recordはこの特殊な名前を見つけると、それらが他のパラメータと結び付けられているとみなし、モデルのカラムの種類に合ったコンストラクタが与えられているとみなします。例:

<%= date_select :person, :birth_date %>

上のコードから以下の出力が得られます(煩雑さを避けるため実際のオプション値を省略しています)。

<select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select>
<select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select>
<select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select>

ここから以下のようなparamsハッシュを得られます。

{'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}

上がPerson.new (またはPerson.update)に与えられると、Active Recordはこれらのパラメータがbirth_date属性を構成するために使用されなければならないことを理解し、接尾語付きの情報を使用します。この情報は、Date.civilなどの関数にどのような順序でこれらのパラメータを渡さなければならないかを決定するのに使われます。

4.3 共通のオプション

どちらのヘルパーファミリーでも、個別のセレクトタグを生成するためのコア機能は共通なので、多くのオプションが同じように使えます。特にRailsでは、どちらのファミリーでも年のオプションはデフォルトで現在の年の前後5年が使用されます。この範囲が適切でない場合は:start_yearオプションと:end_yearオプションを使用して上書きできます。利用できるすべてのオプションを知りたい場合はAPIドキュメントを参照してください。

経験則から言うと、モデルオブジェクトを扱うのであればdate_selectを使用するのがよく、その他の場合、たとえば日付でフィルタするなどの検索フォームで使用するのであればselect_dateを使用するのがよいでしょう。

ビルトインのデートピッカー (date picker) は日付と曜日が連動してくれないなど、あまりできがよくないことが多いようです。

4.4 個別のコンポーネント

日付のうち、たとえば年だけ、月だけのコンポーネントを表示したくなることがあります。Railsでは日付/時刻の個別の要素を扱うためのselect_yearselect_monthselect_dayselect_hourselect_minuteselect_secondヘルパーが用意されています。これらのヘルパーは比較的単純なつくりになっています。これらのヘルパーでは、その日付時刻コンポーネントの要素名をそのまま入力フィールド名として生成します。たとえばselect_yearヘルパーを使用すれば"year"フィールドが生成され、select_monthを使用すれば"month"が生成されるといった具合です。:field_nameオプションを使用してこの名前をカスタマイズすることもできます。:prefixオプションの動作はselect_dateselect_timeのときと同じで、デフォルト値も同じです。

1番目のパラメータでは、選択されるべきパラメータを指定します。使用できるのはDate、Time、DateTimeのいずれかのインスタンスで、それらに応じて関連するコンポーネントまたは数値が取り出されます。例:

<%= select_year(2009) %>
<%= select_year(Time.now) %>

現在の年が2009年であれば上のコードの出力結果は同じになり、値は取り出されてparams[:date][:year]に保存されます。

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

ファイルのアップロードはアプリケーションでよく行われるタスクの1つです (プロフィール写真のアップロードや、処理したいCSVファイルのアップロードなど)。ファイルのアップロードでぜひとも気を付けなければならないのは、出力されるフォームのエンコードは 必ず "multipart/form-data"でなければならないという点です。form_forヘルパーを使用すれば、この点は自動的に処理されます。form_tagを使用してファイルアップロードを行なう場合は、以下の例に示したようにエンコードを明示的に指定しなければなりません。

以下の2つはどちらもファイルアップロードのフォームです。

<%= form_tag({action: :upload}, multipart: true) do %>
  <%= file_field_tag 'picture' %>
<% end %>

<%= form_for @person do |f| %>
  <%= f.file_field :picture %>
<% end %>

Railsでは他と同様、ベアボーンヘルパーのfile_field_tagとモデル指向のfile_fieldが両方提供されています。他のヘルパーと唯一異なる点は、ファイル入力のデフォルト値を設定できないことです(実際、設定する意味がありません)。そしてご想像のとおり、アップロードされたファイルはベアボーンヘルパーの方ではparams[:picture]に保存され、モデル指向のヘルパーの方ではparams[:person][:picture]に保存されます。

5.1 アップロード可能なファイル

paramsハッシュに含まれるこのオブジェクトは、IOクラスのサブクラスのインスタンスです。このオブジェクトは、アップロードされるファイルのサイズに応じて、StringIOであったり、Fileクラスのインスタンス(実態は一時ファイルとして保存される)になったりします。どちらのヘルパーを使用した場合でも、オブジェクトにはoriginal_filename属性とcontent_type属性が含まれます。original_filename属性に含まれる名前は、ユーザーのコンピュータ上にあるファイルの名前です。content_type属性には、アップロードの終わったファイルのMIMEタイプが含まれます。以下のスニペットでは、#{Rails.root}/public/uploadsでアップロードされたコンテンツを、元と同じ名前で保存します(フォームは上の例と同じものを使用したとします)。

def upload
  uploaded_io = params[:person][:picture]
  File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
    file.write(uploaded_io.read)
  end
end

ファイルがアップロードされた後にはやらなければならないことがたくさんあります。ファイルをどこに保存するのかの決定 (ローカルディスク上か、Amazon S3か、など)、モデルとの関連付けの他に、画像であればサイズの変更やサムネイルの生成が必要になることもあります。これらの事後処理は本ガイドの範疇を超えるのでここでは扱いませんが、これらの処理を助けるライブラリがいくつもあることは知っておいてよいと思います。その中でもCarrierWavePaperclipの2つが有名です。

ユーザーがファイルを選択しないでアップロードを行なうと、対応するパラメータには空文字列が置かれます。

5.2 Ajaxを扱う

非同期のファイルアップロードフォームの作成は、他のフォームのようにform_forremote: trueを指定すれば済むというわけにはいきません。Ajaxフォームのシリアライズは、ブラウザ内で実行されるJavaScriptによって行われます。そしてブラウザのJavaScriptは(危険を避けるため)ローカルのファイルにアクセスできないようになっているので、JavaScriptからアップロードファイルを読み出すことができません。これを回避する方法として最も一般的なのは、非表示のiframeをフォーム送信の対象として使用することでしょう。

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

これまで説明したように、form_forおよびfields_forによって生成されるオブジェクトは、FormBuilder (またはそのサブクラス) のインスタンスです。フォームビルダーは、ある1つのオブジェクトのフォーム要素を表示するために必要なものをカプセル化します。独自のフォーム用のヘルパーを普通の方法で自作することもできますし、FormBuilderのサブクラスを作成してそこにヘルパーを追加することもできます。例:

<%= form_for @person do |f| %>
  <%= text_field_with_label f, :first_name %>
<% end %>

上のコードは以下のように置き換えることもできます。

<%= form_for @person, builder: LabellingFormBuilder do |f| %>
  <%= f.text_field :first_name %>
<% end %>

上のコードのために、以下のようなLabellingFormBuilderクラスを定義しておきます。

class LabellingFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(attribute, options={})
    label(attribute) + super
  end
end

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

def labeled_form_for(record, options = {}, &block)
  options.merge! builder: LabellingFormBuilder
  form_for record, options, &block
end

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

<%= render partial: f %>

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

7 パラメータの命名ルールを理解する

ここまで説明したように、フォームから受け取る値はparamsハッシュのトップレベルに置かれるか、他のハッシュの中に入れ子になって含まれます。たとえば、Personモデルの標準的なcreateアクションでは、params[:person]はその人物について作成されるすべての属性のハッシュとなるでしょう。paramsハッシュには配列やハッシュの配列などを含めることもできます。

原則として、HTMLフォームはいかなる構造化データについても関知しません。フォームが生成するのはすべて名前と値のペアであり、これらは単なる文字列です。これらのデータをアプリケーション側で参照した時に配列やハッシュになっているのは、Railsで使用されている命名ルールのパラメータのおかげです。

7.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'}}}

パラメータ名が重複している場合は、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]が電話番号の配列になります。

7.2 組み合わせの技法

これらの2つの概念を混ぜ合わせることもできます。たとえば、前述の例のようにハッシュの要素の1つを配列にするか、ハッシュの配列を使用することができます。他にも、以下のようにフォームの一部を繰り返すことで、住所をいくつでも作成できるようなフォームも考えられます。

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

上のフォームによってparams[:addresses]ハッシュが作成されます。これはline1line2cityをキーに持つハッシュとなります。入力された名前が現在のハッシュに既にある場合は、新しいハッシュに値が追加されるようになります。

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

配列パラメータは、check_boxヘルパーとの相性がよくありません。HTMLの仕様では、オンになっていないチェックボックスからは値が送信されません。しかし、チェックボックスから常に値が送信される方が何かと便利です。そこでcheck_boxヘルパーは、同じ名前で予備の隠し入力を作成することで、本来送信されないはずのチェックボックス値が見かけ上送信されるようにしています。チェックボックスがオフになっていると隠し入力値だけが送信され、チェックボックスがオンになっていると本来のチェックボックス値と隠し入力値が両方送信されますが、このとき優先されるのは本来のチェックボックス値の方です。従って、このように重複した値送信に対して配列パラメータを使用するとRailsが混乱することがあります。その理由は、入力名が重複している場合はそこで新しい配列要素が作成されるからです。これを回避するためには、check_box_tagを使用するか、配列をやめてハッシュを使用してください。

7.3 フォームヘルパーを使用する

前の節ではRailsのフォームヘルパーをまったく使用していませんでした。もちろん、このように入力名を自分でこしらえてtext_field_tagなどのヘルパーに渡してもよいのですが、Railsにはさらに高度なサポートがあります。そのための便利な道具は、form_forfields_forの名前パラメータ、そしてヘルパーが引数に取る:indexオプションの2つです。

複数の住所をそれぞれ編集できるフィールドを持つフォームを作ることもできます。例:

<%= form_for @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 %>

ここでは1人の人物が2つの住所 (idは23と45) を持てるものとします。これによって得られる出力は以下のようなものになります。

<form accept-charset="UTF-8" action="/people/1" class="edit_person" id="edit_person_1" method="post">
  <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'}}}}

Railsは、これらの入力がpersonハッシュの一部でなければならないことを認識してくれます。これが可能なのは、最初のフォームビルダーでfields_forを呼び出してあるからです。:indexオプションを指定すると、入力はperson[address][city]のような名前の代わりに、住所と都市名の間に [ ] で囲まれたインデックスが挿入された名前が使用されます。このようにしておくと、修正すべきAddressレコードを簡単に指定できるので何かと便利です。他の意味を持つ数字を渡したり、文字列やnilを渡すこともできます。これらは、作成される配列パラメータの中に置かれます。

入力名の最初の部分(先の例のperson[address]など)を明示的に示すことで、より複雑なネスティングを作成することもできます。

<%= fields_for 'person[address][primary]', address, index: address do |address_form| %>
  <%= address_form.text_field :city %>
<% end %>

上のコードから以下のような入力が作成されます。

<input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="bologna" />

Railsの一般的なルールとして、最終的な入力名は、fields_forform_forに与えられた名前、インデックス値、そして属性名を連結したものになります。text_fieldなどのヘルパーに:indexオプションを直接渡してもよいのですが、入力コントロールの1つ1つで指定するより、フォームビルダーのレベルで一度指定する方が、たいていの場合繰り返しが少なくて済みます。

名前に[]を追加して:indexオプションを省略する略記法もあります。以下はindex: addressと指定した場合と同じ結果になります。

<%= fields_for 'person[address][primary][]', address do |address_form| %>
  <%= address_form.text_field :city %>
<% end %>

従って、上の結果はその前の例とまったく同じになります。

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

外部リソースに対して何らかのデータを渡す必要がある場合も、Railsのフォームヘルパーを使用してフォームを作成する方がやはり便利です。しかし、その外部リソースに対してauthenticity_tokenを設定しなければならない場合にはどうしたらよいでしょう。これは、form_tagオプションにauthenticity_token: 'your_external_token'パラメータを渡すことで実現できます。

<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %>
  Form contents
<% end %>

支払用ゲートウェイなどの外部リソースに対してときおりデータを送信することがある場合、フォームで使用できるフィールドは外部APIによって制限されてしまいます。そのようなときにはauthenticity_token隠しフィールドを一切生成しないようにしたいものです。フィールド生成を抑制するには、:authenticity_tokenオプションにfalseを渡します。

<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %>
  Form contents
<% end %>

form_forでも同じ方法が使用できます。

<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
  Form contents
<% end %>

あるいは、authenticity_tokenフィールドの生成を抑制することもできます。

<%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
  Form contents
<% end %>

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

最初は単一のオブジェクトを編集していただけのシンプルなフォームも、やがて成長し複雑になるものです。たとえば、Personを1人作成するのであれば、そのうち同じフォームで複数の住所レコード(自宅、職場など)を登録できるようにしたくなることでしょう。後でPersonを編集するときに、必要に応じて住所の追加・削除・変更が行えるようにする必要もあります。

9.1 モデルを構成する

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_for @person do |f| %>
  Addresses:
  <ul>
    <%= f.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ヘルパーはその関連付けのすべての要素を一度ずつ出力します。特に、Personに住所が登録されていない場合は何も出力しません。フィールドのセットが少なくとも1つはユーザーに表示されるように、コントローラで1つ以上の空白の子を作成しておくというのはよく行われるパターンです。以下の例では、Personフォームを新たに作成したときに2組の住所フィールドが表示されるようになっています。

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

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

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

:addresses_attributesハッシュのキーはここでは重要ではありません。各アドレスのキーが重複していなければそれでよいのです。

関連付けられたオブジェクトが既に保存されている場合、fields_forメソッドは、保存されたレコードのidを持つ隠し入力を自動的に作成します。fields_forinclude_id: falseを渡すことでこの自動生成をオフにできます。自動生成をオフにすることがあるとすれば、HTMLが有効でなくなってしまうような場所にinputタグが生成されないようにする場合や、子がidを持たないORM (オブジェクトリレーショナルマッピング) を使用したい場合があります。

9.3 コントローラ

コントローラ内でパラメータをモデルに渡す前に、定番のパラメータのホワイトリストチェックを行いましょう。

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

private
  def person_params
    params.require(:person).permit(: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で値が1またはtrueの組み合わせがあると、そのオブジェクトは削除されます。以下のフォームではユーザーが住所を削除できるようになっています。

<%= form_for @person do |f| %>
  Addresses:
  <ul>
    <%= f.fields_for :addresses do |addresses_form| %>
      <li>
        <%= addresses_form.check_box :_destroy %>
        <%= addresses_form.label :kind %>
        <%= addresses_form.text_field :kind %>
        ...
      </li>
    <% end %>
  </ul>
<% end %>

コントローラ内の、ホワイトリストチェックの終わったパラメータを更新して、_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は、フォームから送信された属性のハッシュ1つ1つについて呼び出されます。このprocがfalseを返す場合、Active Recordはそのハッシュに関連付けられたオブジェクトを作成しません。以下の例では、kind属性が設定されている場合にのみ住所オブジェクトを生成します。

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

代りにシンボル:all_blankを渡すこともできます。このシンボルが渡されると、すべての値が空欄のレコードを受け付けなくなるprocが生成されます。ただし_destroyの場合はどんな値であっても受け付けます。

9.6 その場でフィールドを追加する

多くのフィールドセットを事前に出力する代わりに、[新しい住所を追加] ボタンを押したときだけこれらのフィールドをその場で追加するようにしたいこともあるでしょう。残念ながらRailsではこのためのビルトインサポートは用意されていません。フィールドセットをその場で生成する場合に気を付けたいのは、関連する配列のキーが重複しないようにすることです。JavaScriptで現在の日時を取得して数ミリ秒の時差からユニークな値を得るのが定番の手法です。