このガイドはフォームヘルパーとその引数について網羅的に説明するものではありません。完全なリファレンスについては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="✓" /> <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_tag
、label_tag
、text_field_tag
、submit_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="✓" /></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_tag
やsubmit_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にmethod
とclass
が追加されてしまいました。たとえハッシュを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_tag
やcheck_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_field
、text_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.erb
でform_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="✓" /> <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="✓" /> <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はフォームのclass
とid
を自動的に設定してくれます。この場合、記事を作成するフォームにはid
と、new_article
というclass
が与えられます。もし仮にidが23の記事を編集しようとすると、class
はedit_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="✓" /> <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_tag
とoptions_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に設定され、表示の
sizeは
1になり、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.update
にparams
ハッシュを渡した時に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
を併用する必要があります。モデルオブジェクトを使って作業する場合、select
をselect_tag
およびoptions_for_select
と組み合わせた場合と同様、collection_select
をselect_tag
およびoptions_from_collection_for_select
と組み合わせます。
<%= collection_select(:person, :city_id, City.all, :id, :name) %>
要約すると、options_from_collection_for_select
ヘルパーは「options_for_select
がselect
するべきもの」を「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_date
、select_time
、select_datetime
がベアボーンヘルパーで、date_select
、time_select
、datetime_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_year
、select_month
、select_day
、select_hour
、select_minute
、select_second
ヘルパーが用意されています。これらのヘルパーのつくりは比較的単純で、その日付時刻コンポーネントの要素名をそのまま入力フィールド名として生成します。たとえばselect_year
ヘルパーを使うと「year」フィールドが生成され、select_month
を使うと「month」が生成されるといった具合です。:field_name
オプションでこの名前をカスタマイズすることもできます。:prefix
オプションの動作はselect_date
やselect_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か、など)、モデルとの関連付けの他に、画像であればサイズの変更やサムネイルの生成も必要になることがあります。これらの後処理は本ガイドの範疇を超えるのでここでは扱いませんが、これらの処理を助けるさまざまなライブラリがあることは知っておいてよいと思います。その中でもCarrierWaveとPaperclipの2つが有名です(訳注: Paperclipは開発が終了しました)。
ユーザーがファイルを選択しないままアップロードすると、対応するパラメータには空文字列が置かれます。
5.2 Ajaxを扱う
非同期のファイルアップロードフォームの作成は、他のフォームのようにform_for
にremote: 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]
ハッシュが作成されます。これはline1
、line2
、city
をキーに持つハッシュです。入力された名前が現在のハッシュに既にある場合は、新しいハッシュに値が追加されます。
ただしここで1つ制限があります。ハッシュはいくらでもネストできますが、配列は1階層しか使えません。配列はたいていの場合ハッシュで置き換えられます。たとえば、モデルオブジェクトの配列の代わりに、モデルオブジェクトのハッシュを使えます。このキーではid、配列インデックスなどのパラメータが利用できます。
配列パラメータは、check_box
ヘルパーとの相性がよくありません。HTMLの仕様では、オンになっていないチェックボックスからは値が送信されません。しかし、チェックボックスから常に値が送信される方が何かと便利です。そこでcheck_box
ヘルパーでは、同じ名前で予備の隠し入力を作成しておき、本来送信されないはずのチェックボックス値が見かけ上送信されるようになっています。チェックボックスがオフになっていると隠し入力値だけが送信され、チェックボックスがオンになっていると本来のチェックボックス値と隠し入力値が両方送信されますが、このとき優先されるのは本来のチェックボックス値の方です。従って、このように重複した値送信に対して配列パラメータを使うとRailsが混乱することがあります。その理由は、入力名が重複している場合はそこで新しい配列要素が作成されるからです。これを回避するためには、check_box_tag
を使うか、配列ではなくハッシュをお使いください。
7.3 フォームヘルパーを使う
前の節ではRailsのフォームヘルパーをまったく使っていませんでした。もちろん、このように入力名を自分でこしらえてtext_field_tag
などのヘルパーに渡してもよいのですが、Railsにはさらに高度なサポートがあります。そのための便利な道具は、form_for
とfields_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_for
やform_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_for
にinclude_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_for
にallow_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ガイドは下記のサポーターから継続的な支援を受けています。