fields_forの使い方にかんして調べることが多かったので、ドキュメント を和訳しておく。
fields_for
fields_for(record_name, record_object = nil, options = {}, &block) public
特定のモデルオブジェクトのためにスコープを作るが、form_tagをそれ自体では生成しない。
同じフォームの中で追加でモデルオブジェクトを特定する時に便利。
form_forと用途も目的も似ているが、特徴が少し異なる。
form_forと同様に得知恵のモデルオブジェクトに関するFormBuilderオブジェクトをブロック内に生成し、そのブロック内部ではそのbuilderに対してメソッドを呼び出せる。メソッドは、そのモデルオブジェクトのフィールドを生成したりする際に使われる。フィールドはモデルオブジェクトで二つの変数をいじることができて、一つはname
、すなわちそのフィールドがどう呼ばれるか(すなわちcontroller内のparamsハッシュでどのkeyに対応するか)、二つ目はフィールドの初期値
である。両方の変数が独立に指定されるように、それぞれ引数として別々に渡される。
下の例の <%= fields_for :permission, @person.permission do |permission_fields| %>
の部分である。
<%= form_for @person do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
<%= fields_for :permission, @person.permission do |permission_fields| %>
Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<%= person_form.submit %>
<% end %>
上の例では、check_boxフィールドはpermission[admin]
というname
attributeを持つHTMLのinputタグで表現され、controllerではparams[:permission][:admin]
で表現される。もし@person.permission
がattributeadmin
を持つ既存のレコードであれば、上の例においてcheck_boxは@person.permission.admin
を初期値としてもつ。
しばしば省略されて、モデルオブジェクトの名前だけを引数に渡す。
<%= fields_for :permission do |permission_fields| %>
Admin?: <%= permission_fields.check_box :admin %>
<% end %>
この場合、もし:permission
がインスタンス変数@permission
の名前と(たまたまでも)同じであると、インプットフィールドの初期値は、@permission.admin
の値にセットされる。
もう一つの省略方法としては、モデルオブジェクトを単に渡すこともできる。(第一引数がstringでもsymbolでも内場合は、fields_for
はname
が省略されたものとみなす。)
<%= fields_for @person.permission do |permission_fields| %>
Admin?: <%= permission_fields.check_box :admin %>
<% end %>
この場合、fieldのname
をモデルオブジェクトから持ってくる。例えば、今回のケースで、@person.permission
がPermissionクラスのインスタンスだった場合、フィールドはpermission[admin]
という名前を持つ。
Nested Attributes の例
現在のスコープに属しているオブジェクトがnested attribute writerをあるattributeに関して持っている場合、fields_for
はそのattributeのための新しいスコープを生み出すために有効である。これによって、親オブジェクトとその子オブジェクトの更新を一度に行うことのできるフォームをつくることができる。
nested attribute writerはアソシエーションに対するセッターメソッドである。writerを定義する最も一般的な方法は、モデル定義においてaccepts_nested_attributes_for
をつかうか、適切な名前でメソッドを定義することで可能である(*1
)。例えば、:address
というアソシエーションのattribute writerはaddress_attributes=
で呼びだすことができる。
1対1の時
Personクラスが一つのAddressクラスを持つ時を考える。
*1
の後者の方法、すなわち自分でリーダメソッド、ライターメソッドを定義する場合は以下のようにモデル定義できる。
class Person
def address
@address
end
def address_attributes=(attributes)
# Process the attributes hash
end
end
このモデルはnestedなfields_forを使って以下のように利用できる。
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :address do |address_fields| %>
Street : <%= address_fields.text_field :street %>
Zip code: <%= address_fields.text_field :zip_code %>
<% end %>
...
<% end %>
自分でリーダー、ライターメソッドを定義せずにアソシエーションをつくるメソッドを利用することで表すこともできる。
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
この時、もしフォームを使って紐づく子レコードを削除したい際には、まずはモデル定義において:allow_destroy
オプションを設定する。
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address, allow_destroy: true
end
そして、フォームを使って削除する場合は、_destroy
パラメータを持つフォーム要素を使用することができる。この要素の値がtrueの時、紐づく子レコードは削除される。
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :address do |address_fields| %>
...
Delete: <%= address_fields.check_box :_destroy %>
<% end %>
...
<% end %>
1対多
同様に1対多のケースを考えると、以下の二つの方法でモデル定義をすることができる。
class Person
def projects
[@project1, @project2]
end
def projects_attributes=(attributes)
# Process the attributes hash
end
end
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
fields_for
を使って以下のように書くことができる。fields_for
に与えられたブロックは、子レコードのインスタンスの数だけ繰り返される。(name
に対応する名前のアソシエーションが存在する場合、その紐付いたレコードのインスタンスにアクセスする。)
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
<% if project_fields.object.active? %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
...
<% end %>
ここで、使用されるインスタンスを明示することも可能である。(fields_for
に複数レコードを繰り返し表示してもらうのではなく、それぞれの単体レコードに対してfields_for
でスコープを定義してブロックを生成する。)
<%= form_for @person do |person_form| %>
...
<% @person.projects.each do |project| %>
<% if project.active? %>
<%= person_form.fields_for :projects, project do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
<% end %>
...
<% end %>
fields_for
は前述したように、name
とデフォ値
を取ることができるので、紐づくインスタンスを直接変数で(デフォ値として)指定することも可能である。
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects, @active_projects do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
...
<% end %>
destroyオプションに関しては1対1の時と同様以下のように使用できる。
model定義して、
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects, allow_destroy: true
end
フォームで_destroy
パラメータをセットできるようにする。
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
Delete: <%= project_fields.check_box :_destroy %>
<% end %>
...
<% end %>
複数の紐づくオブジェクトのインスタンスたちのindexを知るには、index
メソッドが使用できる。FormBuilderオブジェクトのインスタンスメソッドである。
<%= form_for @person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
Project #<%= project_fields.index %>
...
<% end %>
...
<% end %>
最後にもうひとつfields_for
のオプションを紹介する。fields_for
は自動で紐づくレコードのIDを持つhidden field
を生成するが、それが要らない時は、include_id: false
とすることで生成されないようにすることができる。