Posted at

Rails fields_for のドキュメントを和訳しました。

More than 3 years have passed since last update.

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]というnameattributeを持つ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_fornameが省略されたものとみなす。)

<%= 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とすることで生成されないようにすることができる。