views:

452

answers:

1

I have some ARes models (see below) that I'm trying to use associations with (which seems to be wholly undocumented and maybe not possible but I thought I'd give it a try)

So on my service side, my ActiveRecord object will render something like

render :xml => @group.to_xml(:include => :customers)

(see generated xml below)

The models Group and Customers are HABTM

On my ARes side, I'm hoping that it can see the <customers> xml attribute and automatically populate the .customers attribute of that Group object , but the has_many etc methods aren't supported (at least as far as I can tell)

So I'm wondering how ARes does it's reflection on the XML to set the attributes of an object. In AR for instance I could create a def customers=(customer_array) and set it myself, but this doesn't seem to work in ARes.

One suggestion I found for an "association" is the just have a method

def customers
  Customer.find(:all, :conditions => {:group_id => self.id})
end

But this has the disadvantage that it makes a second service call to look up those customers... not cool

I'd like my ActiveResource model to see that the customers attribute in the XML and automatically populate my model. Anyone have any experience with this??

# My Services
class Customer < ActiveRecord::Base
  has_and_belongs_to_many :groups
end

class Group < ActiveRecord::Base
  has_and_belongs_to_many :customer
end

# My ActiveResource accessors
class Customer < ActiveResource::Base; end
class Group < ActiveResource::Base; end

# XML from /groups/:id?customers=true

<group>
  <domain>some.domain.com</domain>
  <id type="integer">266</id>
  <name>Some Name</name>
  <customers type="array">
    <customer>
      <active type="boolean">true</active>
      <id type="integer">1</id>
      <name>Some Name</name>
    </customer>
    <customer>
      <active type="boolean" nil="true"></active>
      <id type="integer">306</id>
      <name>Some Other Name</name>
    </customer>
  </customers>
</group>
+3  A: 

ActiveResource does not support associations. But it doen't prevent you from setting/getting complex data to/from a ActiveResource object. Here is how I would implement it:

Server side model

class Customer < ActiveRecord::Base
  has_and_belongs_to_many :groups
  accepts_nested_attributes_for :groups
end

class Group < ActiveRecord::Base
  has_and_belongs_to_many :customers
  accepts_nested_attributes_for :customers
end

Server side GroupsController

def show
  @group = Group.find(params[:id])
  respond_to do |format|
    format.xml { render :xml => @group.to_xml(:include => :customers) }
  end    
end

Client side model

class Customer < ActiveResource::Base
end

class Group < ActiveResource::Base
end

Client side GroupsController

def edit
  @group = Group.find(params[:id])
end

def update
  @group = Group.find(params[:id])
  if @group.load(params[:group]).save
  else
  end
end

Client View: Accessing customer from Group object

# access customers using attributes method. 
@group.customers.each do |customer|
  # access customer fields.
end

Client side: Setting customer to Group object

group.attributes['customers'] ||= [] # Initialize customer array.
group.customers << Customer.new

Edit 1

ActiveResource throws exception when accessing un-initialized attributes ( even if the attribute is present in the remote model)

E.g.:

# server model
class User < ActiveRecord::Base
  # login
  # name
  # email
end

# client model
class User < ActiveResource::Base
end

usr = User.new
usr.first_name # throws exeception
usr.attributes['first_name'] # returns nil

usr.first_name = "John"
usr.first_name # prints "John"
usr.attributes['first_name'] # returns "John"

To work around the initialization issues, I usually monkey patch the ActiveResource class, to add a remote_new method.

config/initializers/active_resource.rb

class ActiveResource::Base
  def self.remote_new options={}
    new(get("new").merge(options))
  end
end

Usage:

usr = User.remote_new
usr.name #nil
usr.login #nil


usr = User.remote_new(:login => "john")
usr.name #nil
usr.login #john

This approach has the added advantage of getting the server set default values for a new object.

KandadaBoggu
Ah, it's the group.attributes['customers'] I was looking for. So ActiveResource is in fact loading the customers XML behind the scenes and creating a Customer object (or array of), but they just don't provide you with the convenience `customers` method. That seems a bit silly. I've made a method:`def customers return attributes['customers'] end` and that works like a charm! Thanks
brad
Updated the answer with clarification. Take a look.
KandadaBoggu