Kevin Sjöberg Kevin Sjöberg - 6 months ago 20
Ruby Question

How does ActiveRecord detect last method call in chain?

Let me visualize that for you.

class Product < ActiveRecord::Base
end

Product.first.title
#=> "My sample product"


Nothing extraordinary here. Just a simple method call. Now take a look at the following example.

class Product < ActiveRecord::Base
def method_missing
end
end

Product.first.title
#=> nil

Product.first
Product.first.title
#=> "My sample product"


How is this possible? In some way they determine the end of the method chain and act upon that? At least thats my theory.

Can anyone explain this behavior?

Answer

You're seeing an artifact of using irb to investigate things.

When you say this:

> Product.first.title
#=> nil

Your method_missing will be called to lazy-load the title method and you get nil.

When you say this:

> Product.first

You're effectively doing this:

> p = Product.first; puts p.inspect

The first Product instance will be loaded and then irb will call inspect on it and AR will add the accessor methods along the way. The result is that Product will now have a title method. Hence, doing this:

> Product.first
> Product.first.title

won't call your method_missing at all as there will be a real title method for Product.first.title to call.

If you try again like this:

> Product.first; nil
> Product.first.title

You'll see two nils.


As far as chaining goes, ActiveRecord doesn't really detect the end, it is just that some method calls naturally require real data from the database and some don't.

If you call where, order, or any of the other querying methods, you get an ActiveRecord::Relation instance back and you can chain more query methods and scopes on that relation object. For example, where (which ActiveRecord::Relation gets by including ActiveRecord::QueryMethods) looks like this:

def where(opts, *rest)
  return self if opts.blank?

  relation = clone
  relation.where_values += build_where(opts, rest)
  relation
end

so it just makes a copy of the current query, adds a few things to the copy, and gives you the copy back.

If you call first, last, to_a, all, any of the Enumerable methods (i.e. you call each), ... then you're asking about specific instances and ActiveRecord will have to execute the query to realize the model instance(s) in question. For example, ActiveRecord::Relation#to_a looks like this:

def to_a
  logging_query_plan do
    exec_queries
  end
end

and all is little more than a wrapper around to_a.

ActiveRecord doesn't really know where the end of the chain is, it just doesn't load anything from the database until it has to so you tell it where the chain ends by saying go forth and retrieve me some data.