Following code was tested with ruby 1.8.7 and Rails 2.x .
Rails recently added named_scope feature
and it is a wonderful thing.
If you don’t know what
named_scope is then you can find out more about it
This article is not about how to use
This article is about how
named_scope does what it does so well.
ActiveRecord has something called
with_scope which is not associated with
The two are entirely separate thing.
named_scope relies on the workings on
with_scope to do its magic.
So in order to understand how
named_scope works first let’s try to understand what
with_scope let’s you add scope to a model in a very extensible manner.
We can see that when
User.all_male is called, it internally calls
and the final sql has both the conditions.
with_scope allows nesting and all the conditions nested together are used to form one single query.
named_scope uses this feature of
with_scope to form one single query from a lot of named scopes.
Writing our own
The best way to learn
named_scope is by implementing the functionality of
We will build this functionality incrementally.
To avoid any confusion we will call our implementation
To keep it simple in the first iteration we will not support any lambda operation.
We will support simple conditions feature. Here is a usage of
We expect following queries to provide right result.
At the top of user.rb add the following lines of code
Now in script/console if we do User then the code will not blow up.
Next we need to implement functionalities so that
mynamed_scope creates class methods like active and male.
What we need is a class where each
mynamed_scope could be stored.
mynamed_scopes are defined on User then we should have a way to get reference to all those
We are going to add class level attribute myscopes which will store all the
mynamed_scopes defined for that class.
This discussion is going to be tricky.
We are storing all
mynamed_scope information in a variable called myscopes.
This will contain all the
mynamed_scopes defined on User.
However we need one more way to track the scoping. When we are executing
User.active then the active
mynamed_scope should be invoked on the User. However when we perform
User.male.active then the
mynamed_scope active should be performed in the scope of
User.male and not directly on User.
This is really crucial. Let’s try one more time. In the case of
User.active the condition that was supplied while defining the
active should be acted on User directly. However in the case of
User.male.active the condition that was supplied while defining
active should be applied on the scope that was returned by
So we need a class which will store
Now the question is when do we create an instance of Scope class. The instance must be created at run time. When we execute
User.male.active, until the run time we don’t know the scope object active has to work upon. It means that
User.male should return a scope and on that scope active will work upon.
proxy_scope is the User class. But for
mynamed_scope ‘active’ gets (User.male) as the proxy_scope.
Also notice that
proxy_scope happens to be the value of self.
Base on all that information we can now write the implementation of
mynamed_scope like this.
At this point of time the overall code looks like this.
What we get is an instance of Scope. What we need is a way to call sql statement at this point of time.
But calling sql can be tricky. Remember each scope has a reference to the
proxy_scope before it. This is the way all the scopes are chained together.
What we need to do is to start walking through the scope graph and if the previous
proxy_scope is an instance of scope then add the condition from the scope to with_scope and then go to the previous
proxy_scope. Keep walking and keep nesting the with_scope condition until we find the end of chain when proxy_scope will NOT be an instance of Scope but it will be a sub class of ActiveRecord::Base.
One way of finding if it is an scope or not is to see if it responds to find(:all). If the
proxy_scope does not respond to find(:all) then keep going back because in the end User will be able to respond to find(:all) method.
Now in script/console you will get undefined method find. That is because find is not implemented by Scope.
Let’s implement method_missing.
Statement User.active.male invokes method ‘male’ and since method ‘male’ is not implemented by Scope, we don’t want to call
proxy_scope yet since this method ‘male’ might be a
mynamed_scope. Hence in the above code a check is done to see if the method that is missing is a declared
mynamed_scope or not. If it is not a
mynamed_scope then the call is sent to
proxy_scope for execution. Pay attention to with_scope. Because of this with_scope all calls to
proxy_scope are nested.
However Scope class doesn’t implement with_scope method. However the first
proxy_scope ,which will be User in our case, implements with_scope method. So we can delegate with_scope method to
proxy_scope like this.
At this point of time the code looks like this
Let’s checkout the result in script/console
named_scope supports a lot more things than what we have shown.
named_scope supports passing lambda instead of conditions and it also supports joins and extensions.
However in the process of building
mynamed_scope we got to see the workings of the