How do rails association methods work?
activerecord, model-associations, ruby-on-rails
Solution
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.
Problem
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?