Making ActiveResource work with nested attributes and fields_for

Hello,

I’m trying to make ActiveResource work with nested attributes and the fields_for tag.

I started with a typical Rails application, with two ActiveRecord models with one to many relationship (one band has many members).

In view ‘bands/new.html.erb’ I have something like this:

<%= form_for @band do |f| %>

<%= f.label: name %>

<%= f.text_field: name %>

<% f.fields_for: members do |member_form| %>

<%= member_form.label: name %>

<%= member_form.text_field: name %>

<%= member_form.label: Instrument %>

<%= member_form.text_field: Instrument %>

<% end %>

<%= f.submit >

<% end %>

In the controller I get this data as follows (in the params hash):

{

band: {

	name: 'band name',

	members_attributes: [

		{name: 'member name' instrument: 'some instrument'}

	]

}

}

When I send this params to ActiveRecord’s new or create it creates the band and members. So far so good.

So I moved the ActiveRecord models into a service and replaced them with ActiveResource models.

However when I send the request to the service ActiveResource change these parameters in a strange way. He turns the ‘members_attributes: […]’ in something like this:

members_attributes: [

{ members_attribute: { name: ‘member name’ instrument: ‘some instrument’ }}

]

And the ActiveRecord on the other side cannot treat this.

Does anyone have any idea how to prevent this behavior (or why it happens)?

In a second attempt I replaced the Band.create by Band.post(nil, {}, params[:band].to_json) (which makes a request directly to the service without going through the ActiveResource::Base#load that appears to be source of the problem), but ActiveResource does a post to ‘/bands/.json’ instead of ‘/bands.json’. I patched ActiveResource’s ‘custom_method_collection_url’ method, so the post goes to the right url, but i have not yet submitted a push request because I don’t know if this will be usefull for everyone.

Actually I’m more concerned with understanding why ActiveResource’s default behavior is so strange.

Anyone know what the purpose of the method ActiveRecord::Base#find_or_create_resource_for_collection? (I know what it does, i just don’t understand why it does it). Wouldn’t it be easier if ActiveResource simply passes my parameters for the service?

If someone can help me I would be grateful.

Realy? No one?

Maybe here is not the place to ask such things. I’ll try somewhere else.

I'm having the same problem. Did you manage to solve it?

  1. http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

  2. If nothing happens, customize view code:

“_member_fields.html.erb”:

<%= builder.label: name %>

<%= builder.text_field: name %>

<%= builder.label: Instrument %>

<%= builder.text_field: Instrument %>

“_form.html_erb”:

<% if @band.new_record? %>

<% f.fields_for :members_attributes do |member_form| %>

<%= render "member_fields, :builder => member_form %>

<% end %>

<% else %>

<% @band.members.each.with_index do |index, member| %>

<% f.fields_for member, “members_attributes[#{index}]” do |member_form| %>

<%= render "member_fields, :builder => member_form %>

<% end %>

<% end %>

<% end %>

I could write something inaccurate, but like that.

Sorry, was a bit overloaded …

What I did to accept XML input for such a case was:

  • put back the “has_many” objects in an array of objects without the _attributes attachment.

The hash would then be:

{

band: {

	name: 'band name',

	members: [

		{name: 'member name' instrument: 'some instrument'}

	]

}

}

for XML that would be:

band name member name
</name>
<instrument>
  some instrument
</instrument>

and similar for JSON.

Then to create a band and it’s member with the assignment:

band = Band.new(params[:band])

which needs a fix to the members= function in the Band class.

class Band

members

has_many :members, :inverse_of => :band
accepts_nested_attributes_for :members

include Xml::FromXmlMembers
end

module Xml
module FromXmlMembers
extend ActiveSupport::Concern
included do
# for xml (on band.members=)
def members=(members)
case members

    when Array
      super(
        members.map |member|
          case member
          when Hash
            Member.new(member)
          else
            member
          end

        end)
    else
      raise 'members MUST be an array; maybe <members type="array"> was forgotten'
    end
  end
end

end
end

The catch is that the function members= that is created by the has_many

relationship

  • expects an array of Member objects
  • but the hash I typically got when parsing incoming XML data
    (and you seem to have here with ActiveResource)
    is an array of attributes hashes.

HTH (not entirely sure …),

Peter

Which version of ActiveResource are you using? I also encounter what you are encountering while using ActiveResource 3.2.1.

In 3.0.* there was “ActiveResource::Base.include_root_in_json” which you could set to false. I am not positive if that’s the exact stumbling point, but it feels like it is.

There’s a difference between ActiveModel 3.0.* and ActiveModel 3.2.*

ActiveModel 3.0.*

Put a new file in your initializers with this code below and see if it helps:

module ActiveResource
class Base
self.include_root_in_json = false
end
module Formats
module JsonFormat
def decode(json)
ActiveSupport::JSON.decode(json)
end
end
end
end

module ActiveModel
module Serializers
module JSON
def as_json(options = nil)
hash = serializable_hash(options)
if include_root_in_json
custom_root = options && options[:root]
hash = { custom_root || self.class.model_name.element => hash }
end
hash
end

  def from_json(json)
    hash = ActiveSupport::JSON.decode(json)
    hash = hash.values.first if include_root_in_json
    self.attributes = hash
    self
  end
end

end
end

I am creating application which requires User management of various
levels for authorization of different level of users.

I want several models:
Admin_user
Project Manager
Company
Clients
Account : have many users of all level.Every user have account.

See below the relations between our models:

1. Admin_user : have many project managers and can give rights to
project manager.It can add and delete Project managers.Admin can
access or manage any level of this application i.e. Its a SUPER USER.

2.Project manager : have many companies.It can create and delete many
companies. Project Manager can also add new project manager if
Admin_user give right to him to create new project manager.its all
depends on Admin_user to give or take rights from PM.

3. Companies : can create and delete many client_users but cannot
create any company in th same level.Company also start or stop rights
of client.

4.Client :can have account login and use services provided by company
nothing more than that.

This is my model Association. But i m confused what can i make first a
Account model or a Admin_user model.

Thanks

Attachments:
http://www.ruby-forum.com/attachment/7125/New_Text_Document.txt

I am creating application which requires User management of various
levels for authorization of different level of users.

I want several models:
Admin_user
Project Manager
Company
Clients
Account : have many users of all level.Every user have account.

Don't have all those different models. Just have model User and use
Roles to limit capabilities of different user types. Have a look at
the cancan gem.

Colin