Rails Nested Resource Issues

Martyn Taylor mtaylor at redhat.com
Fri Sep 21 14:38:49 UTC 2012


On 09/21/2012 12:49 PM, Dmitri Dolguikh wrote:
> On 21/09/12 12:25 PM, Tomas Sedovic wrote:
>> On 09/20/2012 02:20 PM, Martyn Taylor wrote:
>>> Gents.
>>>
>>> I'm having some trouble with getting nested resources working properly
>>> from bespoke XML/JSON in RESTful API.  There seems to be some
>>> fundamental problems with the way rails works, which means we have to
>>> write a lot of bespoke code to get it working, possibly even having to
>>> monkey patch Rails itself.
>>>
>>> I've written an example rails app that highlights the problem and put a
>>> detailed description in the README you can find it here:
>>>
>>> https://github.com/mtaylor/Rails-Nested-Resource-Issues
>>>
>>> I've ran into this issue on IME.  But it's going to affect us across 
>>> all
>>> rails projects with REST APIs, so we could do with discussing this 
>>> issue
>>> and coming up with the best possible solution that results in as little
>>> replication as possible.
>>>
>>> Please read through the problem and let me know if you have any better
>>> ideas that the solutions I have proposed.
>>>
>>> Regards
>>>
>>> Martyn
>>
>>
>> I'm beginning to lean towards the custom serialization and 
>> deserialization. We'll have explicit control over the input/output 
>> formats which is a good thing for API stability.
>>
>> However, there may be another issue that's reasonably close to what 
>> we expected the default behaviour would be.
>>
>> If I understand this correctly, the issue isn't really behaviour of 
>> the `attribute=` setters but of `Model.new`[1] and 
>> `@model.update_attributes`[2] methods.
>>
>> We could insert a base class into the model inheritance chain and 
>> either "fix" the methods there or add new ones that do bulk attribute 
>> operations correctly.
>>
>> Pseudocode:
>>
>> class NestedModels < ActiveRecord::Base
>>   def initialize(attributes = nil, options = {})
>>     if attributes
>>       attributes = attributes.clone
>>       get_nested_attribute_names.each do |name|
>>         attributes[name + '_attributes'] = attributes[name]
>>     attributes.delete name
>>       end
>>     end
>>     super.initialize(attributes, options)
>>   end
>>
>>   def update_attributes(attributes, options = {})
>>     # TODO, similar to initialize
>>   end
>>
>>   def update_attributes!(attributes, options = {})
>>     # TODO, similar to update_attributes
>>   end
>>
>>   def get_nested_attributes
>>     # TODO: use introspection to figure out attribute names passed to 
>> accepts_nested_attributes_for in the model declaration
>>   end
>> end
>>
>> class User < NestedModels
>>   has_many :addresses
>>
>>   accepts_nested_attributes_for :addresses
>>   attr_accessible :name, :addresses, :addresses_attributes
>> end
>>
>>
>> now calling:
>>
>>     User.new({"name"=>"Joe Bloggs", "addresses"=>[{"street"=>"Church 
>> Street"}]})
>>
>> would work because ActiveRecord::Base would receive:
>>
>>     {"name"=>"Joe Bloggs", 
>> "addresses_attributes"=>[{"street"=>"Church Street"}]}
>>
>>
>> This is similar to the monkeypatching proposal but it's safer. We 
>> would not be modifying any of the Rails' internals. Rather, we'd 
>> explicitly specify which models should have this modified behaviour.
>>
>> We could probably also do this by including a module with the 
>> overrides instead of using inheritance.
>
> Keep in mind that ActiveRecord provides builder methods for 
> associations [3]. In the example you used:
>
>    User.address.build(:street => "Church street")
>
> or if you already have an instance of User:
>
>    @user.address.build(:street => "Church street")
>
>
> There are similar methods for one-to-many associations, which have the 
> benefit of elements of collection not being persisted immediately (as 
> opposed to adding an element to a collection directly).
>
Yeah this would work too.  Really I want to try and solve the issues in 
one place.  Using the builders would require a bit of work in checking  
params and building the relevant objects.  We may require many levels of 
nested resources.  Importing an image for example may have 4 levels of 
resources.  So this could get a bit messy.

>
> If you are inclined to solve the problem on the model level, you could 
> add a factory method that accepts a hash, and returns a 
> fully-assembled instance, nested resources and all, with no monkey 
> patching involved.

I'm by no means inclined to solve this at the model level but am hoping 
whatever solution we chose is relatively contained and can be used 
across projects without too much replication.  This  method would 
certainly be better than messing with native rails methods for sure.
>
>
> -d
>
>
>
>>
>> [1]: 
>> https://github.com/rails/rails/blob/9b5309fb6819d8f2a1b31e44ba61e682272c7aa3/activerecord/lib/active_record/base.rb#L481
>> [2]: 
>> https://github.com/rails/rails/blob/81542f95d25825a7d3eff87d6f706661bf553b18/activerecord/lib/active_record/persistence.rb#L211
> [3]: 
> http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html 


Thanks

Martyn
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.fedorahosted.org/pipermail/aeolus-devel/attachments/20120921/14b37a34/attachment.html>


More information about the aeolus-devel mailing list