Given the following two models:
class Company < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments
end
class Department < ActiveRecord::Base
belongs_to :company
end
I can now create a 开发者_如何学Ccompany and its departments in one go:
@company = Company.create! params[:company]
In this example params[:company]
is expected to look like this:
params[:company] = {
:name => 'Foo Inc',
:departments_attributes => {
1 => { :name => 'Management' },
2 => { :name => 'HR' }
}
}
Notice the :departments_attributes
key!
But if I convert to XML using @company.to_xml
, I get the following:
<company>
<id type="integer">1</id>
<name>Foo Inc</activity>
<departments type="array">
<department>
<id type="integer">1</id>
<company-id type="integer">1</company-id>
<name>Management</name>
</department>
<department>
<id type="integer">2</id>
<company-id type="integer">1</company-id>
<name>HR</name>
</department>
</departments>
</company>
Notice that I here get the nested resources in a container node called <departments>
- not <departments_attributes>
!
Why this inconsistency and is there a way to make Rails accept a POST request using departments instead of departments_attributes as the wrapper?
Am I the only one who thinks this is important when creating API's? It's weird for non-rails folks, that the output can't also be used as input.
How have you solved this - if at all?
There is a good reason for this. When you create an association like has_many :departments
, rails creates several methods for you, including departments
, departments=
, and so on.
Now let's take the case of nested_attributes. I don't know if you were aware of this, but the parameters you pass into the params hash aren't limited to just attributes. They'll work for any method. Take this example:
class Company < ActiveRecord::Base
def iliketurtles= attrs
attrs.split(/\s+/).each{|attr| self.send "#{attr}=", 'turtles'}
end
def iliketurtles
self.attributes.select{|attr, value| value == 'turtles'}.join(' ')
end
end
In this example, any model attributes I pass to iliketurtles=
(as a space-delimited string) will set those attributes' values to "turtles". And calling "Iliketurtles" will give you a space-delimited string of attributes whose value equals turtles. Here's the interesting part. I can now include iliketurtles
in my forms:
<%= f.text_field :iliketurtles %>
or params, directly:
params[:company] = {
:name => 'Foo Inc',
:iliketurtles => 'description type address'
}
So what nested_attributes does is create two more methods, departments_attributes
and departments_attributes=
, "accessors" or "setters and getters", basically. So there are methods created to handle nested attributes. The problem is, you can't name those accessors departments
and departments=
because those method names are already taken by the association.
Is there a better way to do this? Maybe, but it would require a fundamental change to the way rails converts params into attributes, and might limit what developers can do. The ability to use custom accessors is much more powerful than my turtle example would lead you to believe :)
You could always implement a departments_attributes
method, something like:
def departments_attributes
departments.map{ |d| d.attributes }
end
Or something like this...
精彩评论