mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
222 lines
6.8 KiB
Text
222 lines
6.8 KiB
Text
|
h2. Rails nested model forms
|
|||
|
|
|||
|
Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
|
|||
|
|
|||
|
In this guide you will:
|
|||
|
|
|||
|
* do stuff
|
|||
|
|
|||
|
endprologue.
|
|||
|
|
|||
|
NOTE: This guide assumes the user knows how to use the "Rails form helpers":form_helpers.html in general. Also, it’s *not* an API reference. For a complete reference please visit "the Rails API documentation":http://api.rubyonrails.org/.
|
|||
|
|
|||
|
|
|||
|
h3. Model setup
|
|||
|
|
|||
|
To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
|
|||
|
|
|||
|
First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The +fields_for+ form helper will look for this method to decide whether or not a nested model form should be build.
|
|||
|
|
|||
|
If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded.
|
|||
|
|
|||
|
Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the +:address+ attribute, the +fields_for+ form helper will look for a method on the Person instance named +address_attributes=+.
|
|||
|
|
|||
|
h4. ActiveRecord::Base model
|
|||
|
|
|||
|
For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method:
|
|||
|
|
|||
|
h5. has_one
|
|||
|
|
|||
|
<ruby>
|
|||
|
class Person < ActiveRecord::Base
|
|||
|
has_one :address
|
|||
|
accepts_nested_attributes_for :address
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
h5. belongs_to
|
|||
|
|
|||
|
<ruby>
|
|||
|
class Person < ActiveRecord::Base
|
|||
|
belongs_to :firm
|
|||
|
accepts_nested_attributes_for :firm
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
h5. has_many / has_and_belongs_to_many
|
|||
|
|
|||
|
<ruby>
|
|||
|
class Person < ActiveRecord::Base
|
|||
|
has_many :projects
|
|||
|
accepts_nested_attributes_for :projects
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
h4. Custom model
|
|||
|
|
|||
|
As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour:
|
|||
|
|
|||
|
h5. Single associated object
|
|||
|
|
|||
|
<ruby>
|
|||
|
class Person
|
|||
|
def address
|
|||
|
Address.new
|
|||
|
end
|
|||
|
|
|||
|
def address_attributes=(attributes)
|
|||
|
# ...
|
|||
|
end
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
h5. Association collection
|
|||
|
|
|||
|
<ruby>
|
|||
|
class Person
|
|||
|
def projects
|
|||
|
[Project.new, Project.new]
|
|||
|
end
|
|||
|
|
|||
|
def projects_attributes=(attributes)
|
|||
|
# ...
|
|||
|
end
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
|
|||
|
|
|||
|
h3. Views
|
|||
|
|
|||
|
h4. Controller code
|
|||
|
|
|||
|
A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first.
|
|||
|
|
|||
|
Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template:
|
|||
|
|
|||
|
<ruby>
|
|||
|
class PeopleController < ActionController:Base
|
|||
|
def new
|
|||
|
@person = Person.new
|
|||
|
@person.built_address
|
|||
|
2.times { @person.projects.build }
|
|||
|
end
|
|||
|
|
|||
|
def create
|
|||
|
@person = Person.new(params[:person])
|
|||
|
if @person.save
|
|||
|
# ...
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
</ruby>
|
|||
|
|
|||
|
NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an +after_initialize+ callback which is a good way to refactor this.
|
|||
|
|
|||
|
h4. Form code
|
|||
|
|
|||
|
Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
|
|||
|
|
|||
|
h5. Standard form
|
|||
|
|
|||
|
Start out with a regular RESTful form:
|
|||
|
|
|||
|
<erb>
|
|||
|
<% form_for @person do |f| %>
|
|||
|
<%= f.text_field :name %>
|
|||
|
<% end %>
|
|||
|
</erb>
|
|||
|
|
|||
|
This will generate the following html:
|
|||
|
|
|||
|
<html>
|
|||
|
<form action="/people" class="new_person" id="new_person" method="post">
|
|||
|
<input id="person_name" name="person[name]" size="30" type="text" />
|
|||
|
</form>
|
|||
|
</html>
|
|||
|
|
|||
|
h5. Nested form for a single associated object
|
|||
|
|
|||
|
Now add a nested form for the +address+ association:
|
|||
|
|
|||
|
<erb>
|
|||
|
<% form_for @person do |f| %>
|
|||
|
<%= f.text_field :name %>
|
|||
|
|
|||
|
<% f.fields_for :address do |af| %>
|
|||
|
<%= f.text_field :street %>
|
|||
|
<% end %>
|
|||
|
<% end %>
|
|||
|
</erb>
|
|||
|
|
|||
|
This generates:
|
|||
|
|
|||
|
<html>
|
|||
|
<form action="/people" class="new_person" id="new_person" method="post">
|
|||
|
<input id="person_name" name="person[name]" size="30" type="text" />
|
|||
|
|
|||
|
<input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" />
|
|||
|
</form>
|
|||
|
</html>
|
|||
|
|
|||
|
Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute.
|
|||
|
|
|||
|
When this form is posted the Rails parameter parser will construct a hash like the following:
|
|||
|
|
|||
|
<ruby>
|
|||
|
{
|
|||
|
"person" => {
|
|||
|
"name" => "Eloy Duran",
|
|||
|
"address_attributes" => {
|
|||
|
"street" => "Nieuwe Prinsengracht"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</ruby>
|
|||
|
|
|||
|
That’s it. The controller will simply pass this hash on to the model from the +create+ action. The model will then handle building the +address+ association for you and automatically save it when the parent (+person+) is saved.
|
|||
|
|
|||
|
h5. Nested form for a collection of associated objects
|
|||
|
|
|||
|
The form code for an association collection is pretty similar to that of a single associated object:
|
|||
|
|
|||
|
<erb>
|
|||
|
<% form_for @person do |f| %>
|
|||
|
<%= f.text_field :name %>
|
|||
|
|
|||
|
<% f.fields_for :projects do |pf| %>
|
|||
|
<%= f.text_field :name %>
|
|||
|
<% end %>
|
|||
|
<% end %>
|
|||
|
</erb>
|
|||
|
|
|||
|
Which generates:
|
|||
|
|
|||
|
<html>
|
|||
|
<form action="/people" class="new_person" id="new_person" method="post">
|
|||
|
<input id="person_name" name="person[name]" size="30" type="text" />
|
|||
|
|
|||
|
<input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" />
|
|||
|
<input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" />
|
|||
|
</form>
|
|||
|
</html>
|
|||
|
|
|||
|
As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as:
|
|||
|
|
|||
|
<ruby>
|
|||
|
{
|
|||
|
"person" => {
|
|||
|
"name" => "Eloy Duran",
|
|||
|
"projects_attributes" => {
|
|||
|
"0" => { "name" => "Project 1" },
|
|||
|
"1" => { "name" => "Project 2" }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</ruby>
|
|||
|
|
|||
|
You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance.
|
|||
|
|
|||
|
NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
|
|||
|
|
|||
|
TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example.
|