/
multi_model_forms.rdoctest
248 lines (189 loc) · 7.46 KB
/
multi_model_forms.rdoctest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
Accessible Associations
{.document-title}
This chapter describes Hobo's support for nested models in forms.
This is mostly technical background -- beginners should not have to
read more than the introduction.
Contents
{.contents-heading}
- contents
{:toc}
doctest: prepare testapp environment
doctest_require: '../prepare_testapp'
>> ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(StringIO.new(''))
>>
def migrate(renames={})
up, down = Generators::Hobo::Migration::Migrator.run(renames)
ActiveRecord::Migration.class_eval(up)
ActiveRecord::Base.send(:descendants).each { |model| model.reset_column_information }
[up, down]
end
{.hidden}
# Introduction
Using multi-model forms in Hobo is very straightforward:
has_many :posts, :accessible => true
Once you've done that, the default forms that Hobo builds will use the
[input-many](/api_tag_defs/input-many) tag.
`:accessible => true` works for `has_many`, `has_many :through` and
`belongs_to`, but does not work for `has_one` or
`has_and_belongs_to_many`.
It's quite common to also add the `:dependent => :destroy` flag to
accessible associations. This also used to trigger magic in Hobo, but
this additional magic has been removed and replaced with [View
Hints](/manual/viewhints). See the [Rails
rdoc](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html)
for more information on `:dependent => :destroy`.
# Model Support
We'll use rubydoctest to provide our examples for this section. Here
are the models:
>>
class Foo < ActiveRecord::Base
hobo_model
fields { name :string }
has_many :bars, :accessible => true
end
>>
class Bar < ActiveRecord::Base
hobo_model
fields { name :string }
belongs_to :foo
end
>> migrate
The `:accessible => true` option patches in
`Hobo::Model::AccessibleAssociations` to your ActiveRecord model. It
modifies the `bars=` writer function to support assigning an array of
records, an array of hashes, an array of ids, or an empty string.
## Assigning an array of records
The whole array must be assigned -- any records that are not assigned
are deleted from your association.
>> bar1 = Bar.new(:name => "bar1")
>> bar2 = Bar.new(:name => "bar2")
>> foo = Foo.new(:name => "foo1")
>> foo.bars = [bar1, bar2]
>> foo.bars.*.name
=> ["bar1", "bar2"]
>> foo.save!
>> foo.bars = [bar2]
>> foo.bars.*.name
=> ["bar2"]
>> foo.save!
>> bar2.foo.name
=> "foo1"
>> bar1.reload
>> bar1.foo
=> nil
If `:dependent => :destroy` had been set on `has_many :bars`, bar1
would now be deleted from the database. Since it hasn't, it still
exists in the database but has become orphaned.
## Assigning an array of hashes
Assigning an array of hashes maps nicely with how Rails deconstructs
your URI encoded query string. For example, your form can return
foo[bars][0][name]=bar1&foo[bars][0][name]=bar2
which Rails will decode into your params hash as
>> params = {"foo" => {"bars" => [ {"name" => "bar3"}, {"name" => "bar4"}]}}
With Hobo's accessible associations, the params hash may be directly
assigned.
>> foo.attributes = params["foo"]
>> foo.bars.*.name
=> ["bar3", "bar4"]
>> foo.save!
Because these parameters did not include an ID, Hobo created new bar
models. If you include an ID, Hobo looks up the existing record in
the database and modifies it with the parameters assigned.
>> params = {"foo" => {"bars" => [ {:name => "bar3_mod", :id => "#{foo.bars[0].id}"}]}}
>> old_bar3_id = foo.bars[0].id
>> foo.attributes = params["foo"]
>> foo.save!
>> foo.bars.*.name
=> ["bar3_mod"]
>> foo.bars[0].id == old_bar3_id
=> true
## Assigning an array of IDs
While [input-many](/api_tag_defs/input-many) returns an array of
hashes, [select-many](/api_tag_defs/select-many) returns an array of
ids. These ids must have an "@" prepended.
>> params = {"foo" => {"bars" => ["@#{bar1.id}", "@#{bar2.id}"]}}
>> foo.attributes = params["foo"]
>> foo.save!
>> foo.bars.*.name
=> ["bar1", "bar2"]
## Assigning an empty string
You can remove all elements from the association by assigning an empty
array:
>> foo.bars = []
>> foo.bars
=> []
However, there is no way to format a URI query string to make Rails
construct an empty array in its params hash, so Hobo adds a useful
shortcut to it's accessible associations:
>> foo.bars = ""
>> foo.bars
=> []
# View Support
The Rapid tags [input-many](/api_tag_defs/input-many),
[input-all](/api_tag_defs/input-all),
[select-many](/api_tag_defs/select-many) and
[check-many](/api_tag_defs/check-many) all require accessible
associations.
`input-many` may even be used in a nested fashion:
<form>
<field-list:>
<foos-view:>
<input-many>
<field-list:>
<bars-view:>
<input-many>
</input-many>
</bars-view:>
</field-list:>
</input-many>
</foos-view:>
</field-list:>
</form>
You do not need to use the accessible association tags -- standard
inputs acquire the correct `name` for use with accessible associations
when called from the appropriate context. Here's an example form that
will work with the example given above in *Model Support*
<form>
<field-list:>
<bars-view:>
<repeat>
<input:name/>
</repeat>
</bars-view:>
<field-list:>
</form>
# Controller Support
No special code is required in your controllers to support accessible
associations, even if you aren't using a Hobo controller.
# Validations
Validations simply work as you'd expect. The only thing to note is
that validation errors in a child object will cause the parent
object to receive an error message of "..." on the association.
# Transactions
Hobo's accessible associations do not do any explicit saves so any new
child objects are not saved until the parent object is saved. Rails
wraps this save in a transaction, so any save is an all or nothing
deal even though parents and children are saved via different SQL
statements.
# Rails 2.3 nested models
Rails 2.3 includes a functionality similar to Hobo's accessible
associations. The Hobo version is based on an early version of the
Rails functionality, but unfortunately the Rails version changed
significantly between the time that the Hobo version was released and
when Rails 2.3 was released.
For more information on Rails 2.3's `accept_nested_attributes_for`,
see [the Ruby on Rails
blog](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms) or
[Ryan Daigle's blog](http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes).
The two versions use a different model: in Hobo the whole array is
assigned, allowing add, delete or update via a single mechanism. In
rails, there's a different mechanism for adding or deleting objects
from the association, and there's no method for update.
Each version have their pluses and minuses. The Hobo version is
conceptually simpler, but it starts to get unwieldy if there are a
large number of elements in the association.
Both mechanisms are compatible and may be enabled simultaneously.
It's certainly possible that Rapid >=1.1 will acquire tags that will
require `accept_nested_attributes_for`. However, it's unlikely that
Hobo will drop support for accessible associations unless ActiveRecord
itself changes significantly.