Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do rails association methods work?

Tags:

How do rails association methods work? Lets consider this example

class User < ActiveRecord::Base
   has_many :articles
end

class Article < ActiveRecord::Base
   belongs_to :user
end

Now I can do something like

@user = User.find(:first)
@user.articles

This fetches me articles belonging to that user. So far so good.

Now further I can go ahead and do a find on these articles with some conditions.

@user.articles.find(:all, :conditions => {:sector_id => 3})

Or simply declare and associations method like

class User < ActiveRecord::Base
   has_many :articles do
     def of_sector(sector_id)
       find(:all, :conditions => {:sector_id => sector_id})
     end
   end
end

And do

@user.articles.of_sector(3)

Now my question is, how does this find work on the array of ActiveRecord objects fetched using the association method? Because if we implement our own User instance method called articles and write our own implementation that gives us the exact same results as that of the association method, the find on the fetch array of ActiveRecord objects wont work.

My guess is that the association methods attach certain properties to the array of fetched objects that enables further querying using find and other ActiveRecord methods. What is the sequence of code execution in this this case? How could I validate this?

like image 885
Chirantan Avatar asked Oct 07 '09 05:10

Chirantan


People also ask

What is the difference between Has_one and Belongs_to?

They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile , then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user . To determine who "has" the other object, look at where the foreign key is.

How is polymorphic association set up in Rails?

The basic structure of a polymorphic association (PA)sets up 2 columns in the comment table. (This is different from a typical one-to-many association, where we'd only need one column that references the id's of the model it belongs to). For a PA, the first column we need to create is for the selected model.


1 Answers

How it actually works is that the association object is a "proxy object". The specific class is AssociationProxy. If you look at line 52 of that file, you'll see:

instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }

By doing this, methods like class no longer exist on this object. So if you call class on this object, you'll get method missing. So, there a method_missing implemented for the proxy object that forwards the method call to the "target":

def method_missing(method, *args)
  if load_target
    unless @target.respond_to?(method)
      message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
      raise NoMethodError, message
    end

    if block_given?
      @target.send(method, *args)  { |*block_args| yield(*block_args) }
    else
      @target.send(method, *args)
    end
  end
end

The target is an Array, so when you call class on this object, it says it's an Array, but that's just because the target is an Array, the actual class is an AssociationProxy, but you can't see that anymore.

So all the methods that you add, such as of_sector, get added to the association proxy, so they get called directly. Methods like [] and class aren't defined on the association proxy, so they get sent to the target, which is an array.

To help you see how this is happening, add this to line 217 of that file in your local copy of association_proxy.rb:

Rails.logger.info "AssociationProxy forwarding call to `#{method.to_s}' method to \"#{@target}\":#{@target.class.to_s}" 

If you don't know where that file is, the command gem which 'active_record/associations/association_proxy' will tell you. Now when you call class on a AssociationProxy, you will see a log message telling you it is sending that to the target, which should make it clearer what is happening. This is all for Rails 2.3.2 and could change in other versions.

like image 80
pjb3 Avatar answered Sep 30 '22 06:09

pjb3