Action View フォームヘルパー

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

このガイドの内容:

  • 検索フォーム、および特定のモデルを表さない一般的なフォームの作成法
  • 特定のデータベースレコードの作成編集を行なう、モデル中心のフォーム作成法
  • 複数の種類のデータからセレクトボックスを生成する方法
  • Railsが提供する日付時刻関連ヘルパー
  • ファイルアップロード用フォームの動作を変更する方法
  • 外部リソース向けにフォームを作成する方法
  • 複雑なフォームの作成方法

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

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

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

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

form_tagは、上のように引数なしで呼び出されると<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タグがないとフォームを正常に送信できないため、divタグは省略できません。最初の隠しinput要素であるutf8は、フォームで指定の文字エンコーディングをブラウザに強制します。これはアクションがGETとPOSTのどちらであってもすべてのフォームで生成されます。

2番目の隠しinput要素であるauthenticity_token要素は クロスサイトリクエストフォージェリへの保護 のためのセキュリティ機能です。この要素はGET以外のすべてのフォームで生成されます (セキュリティ機能が有効になっている場合)。詳細についてはセキュリティガイドを参照してください。

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属性はinputのnameから生成されます (上の例では「q」)。これらのidは、cssでのスタイル追加やJavaScriptによるフォーム制御で使うのに便利です。

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

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

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

form_tagヘルパーは2つの引数を取ります。1つはアクションへのパスで、もう1つはオプションのハッシュです。このハッシュには、フォーム送信のHTTPメソッドや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" />

隠しinputはユーザーには表示されず、種類を問わず事前に与えられた値を保持します。隠しフィールドに含まれている値は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つあります。ルーティングオプションが: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オブジェクトのリストからセレクトボックスを作成する必要がありますが、実はRailsではこの機能を実現する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属性を構成するために使われなければならないことを理解し、接尾語(suffix)付きの情報を利用します。この情報は、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つが有名です(訳注: Paperclipは開発が終了しました)。

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

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つの要素を1個の配列にすることも、複数のハッシュを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のnameパラメータ、そしてヘルパーが引数に取る: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オプションを直接渡してもよいのですが、入力コントロールごとに指定するより、フォームビルダーのレベルで一度指定する方が、たいていの場合繰り返しが少なくて済みます。

名前に[]を追加して: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が1つ生成され、_destroyですべての値が取り除かれます。

9.6 フィールドを動的に追加する

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

フィードバックについて

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

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

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

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

支援・協賛

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

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