Associations across different model types

I'm dying for this functionality between my models.

class Person < ActiveRecord::Base   has_many :notes end

class Note < ActiveResource::Base   belongs_to :person end

Ideally associations should be generic enough to work with any "ActiveModel" type. Associations shouldn't be dealing directly with SQL, this is the job of the target model.

There are a few quirks I want to get started with. Associations try to load construct the necessary finder sql when called. I've been using this little hack for ActiveResource.

class ActiveResource::Base   class << self     def table_name       collection_name     end

    def sanitize_sql(sql)       ActiveRecord::Base.send(:sanitize_sql, sql)     end   end end

Right now, I am using ugly ActiveResource monkey patches to interpret the SQL arguments ActiveRecord sends it.

class ActiveResource::Base   class << self     def find_with_associations(*args)       if args.last.is_a?(Hash) && args.last[:conditions]         if args.first == :all || args.first == :first           if conditions = args.last[:conditions] and conditions.is_a? (String)             scope, id = conditions.gsub(/\.| = /, "-").split("-").last(2)           else             scope, id = conditions.to_a.flatten.map(&:to_s)           end           find_without_associations(args.first, :from => "/ #{scope.humanize.tableize}/#{id}/#{collection_name}.xml")         else           find_without_associations(*args)         end       else         find_without_associations(*args)       end     end     alias_method_chain :find, :associations   end end

This turns this "Note.find(:all, :conditions = 'notes.person_id = 1')" into "Note.find(:all, :from => "/people/1/notes")".

I'll be submitting patches over the next few weeks that hopefully cleanup this muck.

Just wondering if anyone else has been trying to hack something similar together?

I was actually just looking into this for my first project to utilize ActiveResource. I need to be able to create a has_many :through association between one of my ActiveRecord models and an ActiveResource model through an intermediary ActiveRecord model. :slight_smile:

It would be nice to be able to create any association between ActiveRecord resources (local resources) and ActiveResource resources (remote resources) where the foreign key is stored somewhere in the local database. So in other words...

On ActiveRecord models

1) How do you handle local records that are orphaned by a record that's deleted by the remote application? The local application will have no way of knowing that the associated remote record was removed unless it is the one that did the deleting. It won't know it was deleted until it executes a get request on that remote record.

By default ActiveRecord leaves orphaned records as well. The same "destroy" callback applies for both ActiveRecord and ActiveResource models. (However delete_all doesn't exactly work remotely)

2) How do you load the remote objects on habtm & has_many :through associations? With RESTful urls, you either GET the entire collection or GET one element. Do you just do a get request on the entire remote collection and then remove the unassociated objects? That could be a very costly operation with large collections. And I don't think you want to do a GET request on each individual object either.

Some conventions need to come up for plain old has_many relationships. I'm using the nested collection resource (/people/1/notes). But your right, there is no way to efficiently do has_many :through. I think we need to solve the simple ones first (belongs_to, has_one, has_many).

Josh, I'm confused about the example you gave though. You did a belongs_to association on a remote resource to a local resource. How does the remote application know about the local resource? Note.find(:all, :from => "/people/1/notes") indicates that the remote application knows about Person, but you said Person was an ActiveRecord model.

Person was local and Note was remote. The Note uses a simple belongs_to association and just looks up the person with the foreign key it has.

By default ActiveRecord leaves orphaned records as well. The same "destroy" callback applies for both ActiveRecord and ActiveResource models. (However delete_all doesn't exactly work remotely)

Right, but I'd bet that most people are using the dependent option fairly frequently. The destroy callbacks wouldn't work on an ActiveResource model because the local application (and therefore the ActiveResource model) is not notified by the remote application when it destroys a record on its own. For instance, lets say my remote application is something like an Amazon-type store. And my local application is a review website that depends on the product records provided by the RESTful store. In this situation I have:

class Review < ActiveRecord::Base   belongs_to :product end

class Product < ActiveResource::Base   has_many :reviews end

Now what happens when the RESTful store itself removes a product? The RESTful store never notifies my local application of the deletion because it doesn't know anything about my local application. So a destroy callback won't be called by ActiveResource model. Sure I could just let the orphaned reviews stay in my database, but maybe that's not ideal for my particular application.

Person was local and Note was remote. The Note uses a simple belongs_to association and just looks up the person with the foreign key it has.

Okay, but the foreign key is stored in the table for the class saying belongs_to. If Note is remote, where are you storing the foreign key? And how do you call Note.find(:all, :from => "/people/1/notes") on the remote application when "people" are stored locally?

Right, but I'd bet that most people are using the dependent option fairly frequently. The destroy callbacks wouldn't work on an ActiveResource model because the local application (and therefore the ActiveResource model) is not notified by the remote application when it destroys a record on its own. For instance, lets say my remote application is something like an Amazon-type store. And my local application is a review website that depends on the product records provided by the RESTful store. In this situation I have:

class Review < ActiveRecord::Base   belongs_to :product end

class Product < ActiveResource::Base   has_many :reviews end

Now what happens when the RESTful store itself removes a product? The RESTful store never notifies my local application of the deletion because it doesn't know anything about my local application. So a destroy callback won't be called by ActiveResource model. Sure I could just let the orphaned reviews stay in my database, but maybe that's not ideal for my particular application.

I sure hope Amazon (or any third party server) doesn't have access to destroy reviews on my web app. But if I am linking two of my own servers together, I would have the reverse setup on the other server.

# Review server class Review < ActiveRecord::Base   belongs_to :product end class Product < ActiveResource::Base   has_many :reviews end

# Product server class Review < ActiveResource::Base   belongs_to :product end class Product < ActiveRecord::Base   has_many :reviews, :dependent => :destroy end

> Person was local and Note was remote. The Note uses a simple > belongs_to association and just looks up the person with the foreign > key it has.

Okay, but the foreign key is stored in the table for the class saying belongs_to. If Note is remote, where are you storing the foreign key? And how do you call Note.find(:all, :from => "/people/1/notes") on the remote application when "people" are stored locally?

The remote server has to know the existence of people on the other server (reverse relationship on the remote server).

Ah I see, you're assuming you have control over both applications. I don't think that's something you can assume when working with web services. More often than not, you're using a web API to interact with someone else's application.

Yes.

I work with alot of internal web services because our apps are split up.

And if you are working with a third party service, you don't have access to as many features you would if you controlled both.