Making ActiveResource work with nested attributes and fields_for

405 views
Skip to first unread message

Dimas Cyriaco

unread,
Feb 21, 2012, 9:02:02 AM2/21/12
to rubyonra...@googlegroups.com
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.

Dimas Cyriaco

unread,
Feb 22, 2012, 6:51:02 PM2/22/12
to rubyonra...@googlegroups.com
Realy? No one?
Maybe here is not the place to ask such things. I'll try somewhere else.

Esti Alvarez

unread,
Feb 28, 2012, 10:34:51 AM2/28/12
to Ruby on Rails: Talk
I'm having the same problem. Did you manage to solve it?

Valery Kvon

unread,
Feb 28, 2012, 11:00:04 AM2/28/12
to rubyonra...@googlegroups.com
On 28.02.2012, at 19:34, Esti Alvarez wrote:

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


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.

Peter Vandenabeele

unread,
Feb 28, 2012, 11:05:26 AM2/28/12
to rubyonra...@googlegroups.com

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>band name</name>
  <members type="array">
<name>
      member name
    </name>
    <instrument>
      some instrument
    </instrument>
  </members>
</band>

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

Mark Peterson

unread,
Mar 3, 2012, 3:21:10 AM3/3/12
to rubyonra...@googlegroups.com
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.


Mark Peterson

unread,
Mar 3, 2012, 3:40:13 AM3/3/12
to rubyonra...@googlegroups.com
There's a difference between ActiveModel 3.0.* and ActiveModel 3.2.*

ActiveModel 3.0.*
----------
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
    end
  end
end

ActiveModel 3.2.*
----------
module ActiveModel
  module Serializers
    module JSON
      def as_json(options = nil)
        root = include_root_in_json
        root = options[:root] if options.try(:key?, :root)
        if root
          root = self.class.model_name.element if root == true
          { root => serializable_hash(options) }
        else
          serializable_hash(options)

        end
      end
    end
  end
end


I think this has something to do with it. Apologies if I set you on a wild goose chase.

Mark Peterson

unread,
Mar 3, 2012, 3:53:11 AM3/3/12
to rubyonra...@googlegroups.com
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

Anushank Lal

unread,
Mar 3, 2012, 5:12:06 AM3/3/12
to rubyonra...@googlegroups.com
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


--
Posted via http://www.ruby-forum.com/.

Colin Law

unread,
Mar 3, 2012, 5:28:27 AM3/3/12
to rubyonra...@googlegroups.com
On 3 March 2012 10:12, Anushank Lal <li...@ruby-forum.com> wrote:
> 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

>
> 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
>
>
> --
> Posted via http://www.ruby-forum.com/.
>

> --
> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
> To post to this group, send email to rubyonra...@googlegroups.com.
> To unsubscribe from this group, send email to rubyonrails-ta...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
>

--
gplus.to/clanlaw

Reply all
Reply to author
Forward
0 new messages