0

Ruby on Rails: Combining named scopes in rails 2.3 (and before?)

>


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

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir