~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/railties/guides/source/nested_model_forms.textile

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
h2. Rails nested model forms
2
 
 
3
 
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.
4
 
 
5
 
In this guide you will:
6
 
 
7
 
* do stuff
8
 
 
9
 
endprologue.
10
 
 
11
 
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/.
12
 
 
13
 
 
14
 
h3. Model setup
15
 
 
16
 
To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
17
 
 
18
 
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.
19
 
 
20
 
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.
21
 
 
22
 
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=+.
23
 
 
24
 
h4. ActiveRecord::Base model
25
 
 
26
 
For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method:
27
 
 
28
 
h5. has_one
29
 
 
30
 
<ruby>
31
 
class Person < ActiveRecord::Base
32
 
  has_one :address
33
 
  accepts_nested_attributes_for :address
34
 
end
35
 
</ruby>
36
 
 
37
 
h5. belongs_to
38
 
 
39
 
<ruby>
40
 
class Person < ActiveRecord::Base
41
 
  belongs_to :firm
42
 
  accepts_nested_attributes_for :firm
43
 
end
44
 
</ruby>
45
 
 
46
 
h5. has_many / has_and_belongs_to_many
47
 
 
48
 
<ruby>
49
 
class Person < ActiveRecord::Base
50
 
  has_many :projects
51
 
  accepts_nested_attributes_for :projects
52
 
end
53
 
</ruby>
54
 
 
55
 
h4. Custom model
56
 
 
57
 
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:
58
 
 
59
 
h5. Single associated object
60
 
 
61
 
<ruby>
62
 
class Person
63
 
  def address
64
 
    Address.new
65
 
  end
66
 
  
67
 
  def address_attributes=(attributes)
68
 
    # ...
69
 
  end
70
 
end
71
 
</ruby>
72
 
 
73
 
h5. Association collection
74
 
 
75
 
<ruby>
76
 
class Person
77
 
  def projects
78
 
    [Project.new, Project.new]
79
 
  end
80
 
  
81
 
  def projects_attributes=(attributes)
82
 
    # ...
83
 
  end
84
 
end
85
 
</ruby>
86
 
 
87
 
NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
88
 
 
89
 
h3. Views
90
 
 
91
 
h4. Controller code
92
 
 
93
 
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.
94
 
 
95
 
Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template:
96
 
 
97
 
<ruby>
98
 
class PeopleController < ActionController:Base
99
 
  def new
100
 
    @person = Person.new
101
 
    @person.built_address
102
 
    2.times { @person.projects.build }
103
 
  end
104
 
  
105
 
  def create
106
 
    @person = Person.new(params[:person])
107
 
    if @person.save
108
 
      # ...
109
 
    end
110
 
  end
111
 
end
112
 
</ruby>
113
 
 
114
 
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.
115
 
 
116
 
h4. Form code
117
 
 
118
 
Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
119
 
 
120
 
h5. Standard form
121
 
 
122
 
Start out with a regular RESTful form:
123
 
 
124
 
<erb>
125
 
<% form_for @person do |f| %>
126
 
  <%= f.text_field :name %>
127
 
<% end %>
128
 
</erb>
129
 
 
130
 
This will generate the following html:
131
 
 
132
 
<html>
133
 
<form action="/people" class="new_person" id="new_person" method="post">
134
 
  <input id="person_name" name="person[name]" size="30" type="text" />
135
 
</form>
136
 
</html>
137
 
 
138
 
h5. Nested form for a single associated object
139
 
 
140
 
Now add a nested form for the +address+ association:
141
 
 
142
 
<erb>
143
 
<% form_for @person do |f| %>
144
 
  <%= f.text_field :name %>
145
 
  
146
 
  <% f.fields_for :address do |af| %>
147
 
    <%= f.text_field :street %>
148
 
  <% end %>
149
 
<% end %>
150
 
</erb>
151
 
 
152
 
This generates:
153
 
 
154
 
<html>
155
 
<form action="/people" class="new_person" id="new_person" method="post">
156
 
  <input id="person_name" name="person[name]" size="30" type="text" />
157
 
  
158
 
  <input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" />
159
 
</form>
160
 
</html>
161
 
 
162
 
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.
163
 
 
164
 
When this form is posted the Rails parameter parser will construct a hash like the following:
165
 
 
166
 
<ruby>
167
 
{
168
 
  "person" => {
169
 
    "name" => "Eloy Duran",
170
 
    "address_attributes" => {
171
 
      "street" => "Nieuwe Prinsengracht"
172
 
    }
173
 
  }
174
 
}
175
 
</ruby>
176
 
 
177
 
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.
178
 
 
179
 
h5. Nested form for a collection of associated objects
180
 
 
181
 
The form code for an association collection is pretty similar to that of a single associated object:
182
 
 
183
 
<erb>
184
 
<% form_for @person do |f| %>
185
 
  <%= f.text_field :name %>
186
 
  
187
 
  <% f.fields_for :projects do |pf| %>
188
 
    <%= f.text_field :name %>
189
 
  <% end %>
190
 
<% end %>
191
 
</erb>
192
 
 
193
 
Which generates:
194
 
 
195
 
<html>
196
 
<form action="/people" class="new_person" id="new_person" method="post">
197
 
  <input id="person_name" name="person[name]" size="30" type="text" />
198
 
  
199
 
  <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" />
200
 
  <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" />
201
 
</form>
202
 
</html>
203
 
 
204
 
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:
205
 
 
206
 
<ruby>
207
 
{
208
 
  "person" => {
209
 
    "name" => "Eloy Duran",
210
 
    "projects_attributes" => {
211
 
      "0" => { "name" => "Project 1" },
212
 
      "1" => { "name" => "Project 2" }
213
 
    }
214
 
  }
215
 
}
216
 
</ruby>
217
 
 
218
 
You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance.
219
 
 
220
 
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.
221
 
 
222
 
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.
 
 
b'\\ No newline at end of file'