>
While Arel brought the ability to combine scopes through the use of the operator “&”, and the ability to extract the SQL expression out of any scope, there are still a good deal of us mortals out there that has to live in a world of past versions, due various reasons.
I’ve been feeling quite uncomfortable not being able to combine named scopes in a readable and manegable way for some time now. It might be me, but however much I searched the net, I just couldn’t find a solution that satisfied me. So I started looking into the implementation of the named scopes in rails 2.3.10, to devise a solution.
The solution I’ve came up with is available as a plugin. You can download combined_named_scope from github.
To use it, you use the method “combined_named_scope”, instead of “named_scope”. You also are expected to return an array of scope objects, which is what is returned when you call any of the named scopes you’ve created.
For example, let’s say that you have a Fruit model, which come in various colors and sizes.
class Fruit << ActiveRecord::Base
named_scope :orange, :conditions => {:color => 'orange'}
named_scope :red, :conditions => {:color => 'red'}
named_scope :smaller_than, lambda { |this_other_fruit|
{ :conditions => ["size > ?", this_other_fruit.size] }
}
end
Now suppose that you’d like a named scope, a single named scope, that returns fruits that are smaller than a given fruit, but never larger than a cantaloupe, and are red. Here is how you can do it with the combined_named_scope:
class Fruit << ActiveRecord::Base .
.
.
combined_named_scope :red_and_smaller_than_this_and_a_cantaloupe, lambda { |this_fruit|
[ red, smaller_than(this_fruit), smaller_than(Fruit.find(CANTALOUPE_ID)) ]
}
end
Now whenever you use Fruit.red_and_smaller_than_this_or_equal_to(apple)
, it will mean the same as Fruit.red.smaller_than(apple).smaller_than(Fruit.find(CANTALOUPE_ID))
. You can use your new combined named scope as you can use any other named scope. There is one catch, and to describe it, I need to explain how this works, shortly.
The named scopes, as implemented in ActiveRecord 2.3, is in essence a one-way linked-list of Scope objects. Each of these scope objects hold a hash, which holds options that can be given to ActiveRecord::with_scope
as a parameter. In fact, traversing the linked list and issuing successive with_scope
calls is exactly what is done to compose the named scopes, when it comes to collect the records they refer to.
Therefore, the natural solution to combining two named scopes is to combine the one-way linked lists of them. This involves some details which are dependent on how these linked lists are implemented, and I won’t go into them here. If you are curious, the code is there in the combined_named_scope plugin for you to explore.
The catch I mentioned before is this: if you call proxy_options
on any combined named scope, you will only get the last scope in the linked list build by combined_named_scope method. Which named scope that will be, depends on the implementation of the combined_named_scope method, or to say in other words: don’t depend on it.
Happy coding,
–eg