Permalink
Browse files

Started on a Rails guide on nested model forms.

  • Loading branch information...
alloy committed Mar 14, 2009
1 parent 952d1cc commit 0e12b554c9601d0402ea3d5038f72fcace5bfb31
Showing with 177 additions and 0 deletions.
  1. +177 −0 railties/guides/source/nested_model_forms.textile
@@ -0,0 +1,177 @@
+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 be saved when the parent (+person+) is saved.

0 comments on commit 0e12b55

Please sign in to comment.