|
e8550ee0
»
|
jeremy |
2009-05-13 |
Cherry-pick core extensions |
1 |
require 'active_support/core_ext/hash/except' |
| |
2 |
require 'active_support/core_ext/object/try' |
| |
3 |
|
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
4 |
module ActiveRecord |
| |
5 |
module NestedAttributes #:nodoc: |
|
4e50a35f
»
|
josh |
2009-05-28 |
Break up DependencyModule's... |
6 |
extend ActiveSupport::Concern |
|
a2875bec
»
|
brynary |
2009-05-11 |
Use DependencyModule for in... |
7 |
|
| |
8 |
included do |
| |
9 |
class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false |
| |
10 |
self.reject_new_nested_attributes_procs = {} |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
11 |
end |
| |
12 |
|
| |
13 |
# == Nested Attributes |
| |
14 |
# |
| |
15 |
# Nested attributes allow you to save attributes on associated records |
| |
16 |
# through the parent. By default nested attribute updating is turned off, |
| |
17 |
# you can enable it using the accepts_nested_attributes_for class method. |
| |
18 |
# When you enable nested attributes an attribute writer is defined on |
| |
19 |
# the model. |
| |
20 |
# |
| |
21 |
# The attribute writer is named after the association, which means that |
| |
22 |
# in the following example, two new methods are added to your model: |
| |
23 |
# <tt>author_attributes=(attributes)</tt> and |
| |
24 |
# <tt>pages_attributes=(attributes)</tt>. |
| |
25 |
# |
| |
26 |
# class Book < ActiveRecord::Base |
| |
27 |
# has_one :author |
| |
28 |
# has_many :pages |
| |
29 |
# |
| |
30 |
# accepts_nested_attributes_for :author, :pages |
| |
31 |
# end |
| |
32 |
# |
| |
33 |
# Note that the <tt>:autosave</tt> option is automatically enabled on every |
| |
34 |
# association that accepts_nested_attributes_for is used for. |
| |
35 |
# |
| |
36 |
# === One-to-one |
| |
37 |
# |
| |
38 |
# Consider a Member model that has one Avatar: |
| |
39 |
# |
| |
40 |
# class Member < ActiveRecord::Base |
| |
41 |
# has_one :avatar |
| |
42 |
# accepts_nested_attributes_for :avatar |
| |
43 |
# end |
| |
44 |
# |
| |
45 |
# Enabling nested attributes on a one-to-one association allows you to |
| |
46 |
# create the member and avatar in one go: |
| |
47 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
48 |
# params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } } |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
49 |
# member = Member.create(params) |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
50 |
# member.avatar.id # => 2 |
| |
51 |
# member.avatar.icon # => 'smiling' |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
52 |
# |
| |
53 |
# It also allows you to update the avatar through the member: |
| |
54 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
55 |
# params = { :member' => { :avatar_attributes => { :id => '2', :icon => 'sad' } } } |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
56 |
# member.update_attributes params['member'] |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
57 |
# member.avatar.icon # => 'sad' |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
58 |
# |
| |
59 |
# By default you will only be able to set and update attributes on the |
| |
60 |
# associated model. If you want to destroy the associated model through the |
| |
61 |
# attributes hash, you have to enable it first using the |
| |
62 |
# <tt>:allow_destroy</tt> option. |
| |
63 |
# |
| |
64 |
# class Member < ActiveRecord::Base |
| |
65 |
# has_one :avatar |
| |
66 |
# accepts_nested_attributes_for :avatar, :allow_destroy => true |
| |
67 |
# end |
| |
68 |
# |
| |
69 |
# Now, when you add the <tt>_delete</tt> key to the attributes hash, with a |
| |
70 |
# value that evaluates to +true+, you will destroy the associated model: |
| |
71 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
72 |
# member.avatar_attributes = { :id => '2', :_delete => '1' } |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
73 |
# member.avatar.marked_for_destruction? # => true |
| |
74 |
# member.save |
| |
75 |
# member.avatar #=> nil |
| |
76 |
# |
| |
77 |
# Note that the model will _not_ be destroyed until the parent is saved. |
| |
78 |
# |
| |
79 |
# === One-to-many |
| |
80 |
# |
| |
81 |
# Consider a member that has a number of posts: |
| |
82 |
# |
| |
83 |
# class Member < ActiveRecord::Base |
| |
84 |
# has_many :posts |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
85 |
# accepts_nested_attributes_for :posts |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
86 |
# end |
| |
87 |
# |
| |
88 |
# You can now set or update attributes on an associated post model through |
| |
89 |
# the attribute hash. |
| |
90 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
91 |
# For each hash that does _not_ have an <tt>id</tt> key a new record will |
| |
92 |
# be instantiated, unless the hash also contains a <tt>_delete</tt> key |
| |
93 |
# that evaluates to +true+. |
| |
94 |
# |
| |
95 |
# params = { :member => { |
| |
96 |
# :name => 'joe', :posts_attributes => [ |
| |
97 |
# { :title => 'Kari, the awesome Ruby documentation browser!' }, |
| |
98 |
# { :title => 'The egalitarian assumption of the modern citizen' }, |
| |
99 |
# { :title => '', :_delete => '1' } # this will be ignored |
| |
100 |
# ] |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
101 |
# }} |
| |
102 |
# |
| |
103 |
# member = Member.create(params['member']) |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
104 |
# member.posts.length # => 2 |
| |
105 |
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' |
| |
106 |
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen' |
| |
107 |
# |
| |
108 |
# You may also set a :reject_if proc to silently ignore any new record |
| |
109 |
# hashes if they fail to pass your criteria. For example, the previous |
| |
110 |
# example could be rewritten as: |
| |
111 |
# |
| |
112 |
# class Member < ActiveRecord::Base |
| |
113 |
# has_many :posts |
| |
114 |
# accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? } |
| |
115 |
# end |
| |
116 |
# |
| |
117 |
# params = { :member => { |
| |
118 |
# :name => 'joe', :posts_attributes => [ |
| |
119 |
# { :title => 'Kari, the awesome Ruby documentation browser!' }, |
| |
120 |
# { :title => 'The egalitarian assumption of the modern citizen' }, |
| |
121 |
# { :title => '' } # this will be ignored because of the :reject_if proc |
| |
122 |
# ] |
| |
123 |
# }} |
| |
124 |
# |
| |
125 |
# member = Member.create(params['member']) |
| |
126 |
# member.posts.length # => 2 |
| |
127 |
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' |
| |
128 |
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen' |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
129 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
130 |
# If the hash contains an <tt>id</tt> key that matches an already |
| |
131 |
# associated record, the matching record will be modified: |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
132 |
# |
| |
133 |
# member.attributes = { |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
134 |
# :name => 'Joe', |
| |
135 |
# :posts_attributes => [ |
| |
136 |
# { :id => 1, :title => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, |
| |
137 |
# { :id => 2, :title => '[UPDATED] other post' } |
| |
138 |
# ] |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
139 |
# } |
| |
140 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
141 |
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' |
| |
142 |
# member.posts.second.title # => '[UPDATED] other post' |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
143 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
144 |
# By default the associated records are protected from being destroyed. If |
| |
145 |
# you want to destroy any of the associated records through the attributes |
| |
146 |
# hash, you have to enable it first using the <tt>:allow_destroy</tt> |
| |
147 |
# option. This will allow you to also use the <tt>_delete</tt> key to |
| |
148 |
# destroy existing records: |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
149 |
# |
| |
150 |
# class Member < ActiveRecord::Base |
| |
151 |
# has_many :posts |
| |
152 |
# accepts_nested_attributes_for :posts, :allow_destroy => true |
| |
153 |
# end |
| |
154 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
155 |
# params = { :member => { |
| |
156 |
# :posts_attributes => [{ :id => '2', :_delete => '1' }] |
| |
157 |
# }} |
| |
158 |
# |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
159 |
# member.attributes = params['member'] |
| |
160 |
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true |
| |
161 |
# member.posts.length #=> 2 |
| |
162 |
# member.save |
| |
163 |
# member.posts.length # => 1 |
| |
164 |
# |
| |
165 |
# === Saving |
| |
166 |
# |
| |
167 |
# All changes to models, including the destruction of those marked for |
| |
168 |
# destruction, are saved and destroyed automatically and atomically when |
| |
169 |
# the parent model is saved. This happens inside the transaction initiated |
| |
170 |
# by the parents save method. See ActiveRecord::AutosaveAssociation. |
| |
171 |
module ClassMethods |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
172 |
# Defines an attributes writer for the specified association(s). If you |
| |
173 |
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you |
| |
174 |
# will need to add the attribute writer to the allowed list. |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
175 |
# |
| |
176 |
# Supported options: |
| |
177 |
# [:allow_destroy] |
| |
178 |
# If true, destroys any members from the attributes hash with a |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
179 |
# <tt>_delete</tt> key and a value that evaluates to +true+ |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
180 |
# (eg. 1, '1', true, or 'true'). This option is off by default. |
| |
181 |
# [:reject_if] |
| |
182 |
# Allows you to specify a Proc that checks whether a record should be |
| |
183 |
# built for a certain attribute hash. The hash is passed to the Proc |
| |
184 |
# and the Proc should return either +true+ or +false+. When no Proc |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
185 |
# is specified a record will be built for all attribute hashes that |
| |
186 |
# do not have a <tt>_delete</tt> that evaluates to true. |
|
9010ed27
»
|
hardbap |
2009-05-09 |
Allow you to pass :all_blan...  |
187 |
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc |
| |
188 |
# that will reject a record where all the attributes are blank. |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
189 |
# |
| |
190 |
# Examples: |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
191 |
# # creates avatar_attributes= |
| |
192 |
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? } |
|
9010ed27
»
|
hardbap |
2009-05-09 |
Allow you to pass :all_blan...  |
193 |
# # creates avatar_attributes= |
| |
194 |
# accepts_nested_attributes_for :avatar, :reject_if => :all_blank |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
195 |
# # creates avatar_attributes= and posts_attributes= |
| |
196 |
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
197 |
def accepts_nested_attributes_for(*attr_names) |
| |
198 |
options = { :allow_destroy => false } |
| |
199 |
options.update(attr_names.extract_options!) |
| |
200 |
options.assert_valid_keys(:allow_destroy, :reject_if) |
| |
201 |
|
| |
202 |
attr_names.each do |association_name| |
| |
203 |
if reflection = reflect_on_association(association_name) |
| |
204 |
type = case reflection.macro |
| |
205 |
when :has_one, :belongs_to |
| |
206 |
:one_to_one |
| |
207 |
when :has_many, :has_and_belongs_to_many |
| |
208 |
:collection |
| |
209 |
end |
| |
210 |
|
| |
211 |
reflection.options[:autosave] = true |
|
9010ed27
»
|
hardbap |
2009-05-09 |
Allow you to pass :all_blan...  |
212 |
|
| |
213 |
self.reject_new_nested_attributes_procs[association_name.to_sym] = if options[:reject_if] == :all_blank |
| |
214 |
proc { |attributes| attributes.all? {|k,v| v.blank?} } |
| |
215 |
else |
| |
216 |
options[:reject_if] |
| |
217 |
end |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
218 |
|
| |
219 |
# def pirate_attributes=(attributes) |
| |
220 |
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false) |
| |
221 |
# end |
| |
222 |
class_eval %{ |
| |
223 |
def #{association_name}_attributes=(attributes) |
| |
224 |
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]}) |
| |
225 |
end |
| |
226 |
}, __FILE__, __LINE__ |
| |
227 |
else |
| |
228 |
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" |
| |
229 |
end |
| |
230 |
end |
| |
231 |
end |
| |
232 |
end |
| |
233 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
234 |
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's |
| |
235 |
# used in conjunction with fields_for to build a form element for the |
| |
236 |
# destruction of this association. |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
237 |
# |
| |
238 |
# See ActionView::Helpers::FormHelper::fields_for for more info. |
| |
239 |
def _delete |
| |
240 |
marked_for_destruction? |
| |
241 |
end |
| |
242 |
|
| |
243 |
private |
| |
244 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
245 |
# Attribute hash keys that should not be assigned as normal attributes. |
| |
246 |
# These hash keys are nested attributes implementation details. |
| |
247 |
UNASSIGNABLE_KEYS = %w{ id _delete } |
| |
248 |
|
| |
249 |
# Assigns the given attributes to the association. |
| |
250 |
# |
| |
251 |
# If the given attributes include an <tt>:id</tt> that matches the existing |
| |
252 |
# record’s id, then the existing record will be modified. Otherwise a new |
| |
253 |
# record will be built. |
| |
254 |
# |
| |
255 |
# If the given attributes include a matching <tt>:id</tt> attribute _and_ a |
| |
256 |
# <tt>:_delete</tt> key set to a truthy value, then the existing record |
| |
257 |
# will be marked for destruction. |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
258 |
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy) |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
259 |
attributes = attributes.stringify_keys |
| |
260 |
|
| |
261 |
if attributes['id'].blank? |
| |
262 |
unless reject_new_record?(association_name, attributes) |
| |
263 |
send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS)) |
| |
264 |
end |
| |
265 |
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s |
| |
266 |
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
267 |
end |
| |
268 |
end |
| |
269 |
|
| |
270 |
# Assigns the given attributes to the collection association. |
| |
271 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
272 |
# Hashes with an <tt>:id</tt> value matching an existing associated record |
| |
273 |
# will update that record. Hashes without an <tt>:id</tt> value will build |
| |
274 |
# a new record for the association. Hashes with a matching <tt>:id</tt> |
| |
275 |
# value and a <tt>:_delete</tt> key set to a truthy value will mark the |
| |
276 |
# matched record for destruction. |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
277 |
# |
| |
278 |
# For example: |
| |
279 |
# |
| |
280 |
# assign_nested_attributes_for_collection_association(:people, { |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
281 |
# '1' => { :id => '1', :name => 'Peter' }, |
| |
282 |
# '2' => { :name => 'John' }, |
| |
283 |
# '3' => { :id => '2', :_delete => true } |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
284 |
# }) |
| |
285 |
# |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
286 |
# Will update the name of the Person with ID 1, build a new associated |
| |
287 |
# person with the name `John', and mark the associatied Person with ID 2 |
| |
288 |
# for destruction. |
| |
289 |
# |
| |
290 |
# Also accepts an Array of attribute hashes: |
| |
291 |
# |
| |
292 |
# assign_nested_attributes_for_collection_association(:people, [ |
| |
293 |
# { :id => '1', :name => 'Peter' }, |
| |
294 |
# { :name => 'John' }, |
| |
295 |
# { :id => '2', :_delete => true } |
| |
296 |
# ]) |
| |
297 |
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy) |
| |
298 |
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) |
| |
299 |
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
300 |
end |
| |
301 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
302 |
if attributes_collection.is_a? Hash |
| |
303 |
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes } |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
304 |
end |
| |
305 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
306 |
attributes_collection.each do |attributes| |
| |
307 |
attributes = attributes.stringify_keys |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
308 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
309 |
if attributes['id'].blank? |
| |
310 |
unless reject_new_record?(association_name, attributes) |
| |
311 |
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS)) |
| |
312 |
end |
| |
313 |
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s } |
| |
314 |
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy) |
| |
315 |
end |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
316 |
end |
| |
317 |
end |
| |
318 |
|
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
319 |
# Updates a record with the +attributes+ or marks it for destruction if |
| |
320 |
# +allow_destroy+ is +true+ and has_delete_flag? returns +true+. |
| |
321 |
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) |
| |
322 |
if has_delete_flag?(attributes) && allow_destroy |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
323 |
record.mark_for_destruction |
| |
324 |
else |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
325 |
record.attributes = attributes.except(*UNASSIGNABLE_KEYS) |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
326 |
end |
| |
327 |
end |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
328 |
|
| |
329 |
# Determines if a hash contains a truthy _delete key. |
| |
330 |
def has_delete_flag?(hash) |
| |
331 |
ConnectionAdapters::Column.value_to_boolean hash['_delete'] |
| |
332 |
end |
| |
333 |
|
| |
334 |
# Determines if a new record should be build by checking for |
| |
335 |
# has_delete_flag? or if a <tt>:reject_if</tt> proc exists for this |
| |
336 |
# association and evaluates to +true+. |
| |
337 |
def reject_new_record?(association_name, attributes) |
| |
338 |
has_delete_flag?(attributes) || |
| |
339 |
self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes) |
| |
340 |
end |
|
ec8f0458
»
|
alloy |
2009-01-31 |
Add support for nested obje...  |
341 |
end |
|
5dbc9d40
»
|
cainlevy |
2009-02-08 |
Changed API of NestedAttrib... |
342 |
end |