A Fresh Cup is Mike Gunderloy's software development weblog, covering Ruby on Rails and whatever else I find interesting in the universe of software. I'm a full-time Rails developer and contributor, available for long- or short-term consulting, with solid experience in working as part of a distributed team. If you'd like to hire me, drop me a line. I'm also the author of Rails Rescue Handbook and Rails Freelancing Handbook.

Navigation
« Double Shot #387 | Main | Double Shot #386 »
Friday
Feb062009

Digging Into the Named Scope Magic

I got an IM this afternoon from a friend, pointing to part of the Active Record Query Interface guide:

Then you could call Client.males.all to get all the clients who are male. Please note that if you do not specify the all on the end you will get a Scope object back, not a set of records which you do get back if you put the all on the end.


Setting up a simple named scope and testing it in irb certainly seems to indicate that this passage is wrong:

[sourcecode language='ruby']
Client.males.is_a?(Array) => true
Client.males.is_a?(ActiveRecord::NamedScope::Scope)
=> false
[/sourcecode]

And yet, a moment's reflection says that named scopes can't simply be returning arrays of ActiveRecord objects: if they were, there would be no way to compose them.

As with other Rails questions, the easiest way to answer this is to fire up a text editor and dig into the Rails source - in this case, activerecord/lib/active_record/named_scope.rb, which contains the definition of the Scope object and the other named scope apparatus. There you'll find this chunk of code:

[sourcecode language='ruby']
NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size
count sum average maximum minimum paginate first last empty? any?
respond_to?).to_set
[].methods.each do |m|
unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
delegate m, :to => :proxy_found
end
end
[/sourcecode]

So: except for a small set of explicitly-listed methods, the Scope object delegates everything to :proxy_found. And what is that? Read a bit further in the source and you'll find out:

[sourcecode language='ruby']
def proxy_found
@found || load_found
end

def load_found
@found = find(:all)
end
[/sourcecode]

So: for any method not in its small list - say, the inspect that IRB uses to display the value of an expression - the Scope looks at its internal results array, running the find to do so if necessary. Thus we have an object that at first glance appears to be something other than what it really is. Problem solved.

Reader Comments (2)

I went through this same thing last summer trying to understand why ActiveRecord association proxies displayed the way they did in irb. Ruby can be one sneaky little rascal.

February 13, 2009 | Unregistered CommenterRyan

Thanks for this, it explains why i was getting odd results when calling empty? on my anonymous scopes.

January 17, 2010 | Unregistered CommenterDave

PostPost a New Comment

Enter your information below to add a new comment.
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>