It all started when I tweeted:

wish I could get hold of a chain of named_scopes without performing any query (which means it can’t be dynamically composed) #activerecord

My team has currently been performing some refactorings on our Rails app and one of our goals is to move complex find queries into more composable/meaningful/maintainable named scopes. Contrary to my expectations, when you call a named scope, Active Record performs a find(:all) query implicitly as you finish your statement:

class Shirt < ActiveRecord::Base
  named_scope :red, :conditions => {:color => 'red'}
  named_scope :clean, :conditions => {:clean => true}
end
 
>> red_proxy = Shirt.red
  Shirt Load (0.3ms)   SELECT * FROM "shirts" WHERE ("shirts"."color" = 'red')
+----+-------+-------------------------+-------------------------+-------+
| id | color | created_at              | updated_at              | clean |
+----+-------+-------------------------+-------------------------+-------+
| 5  | red   | 2009-11-16 18:36:37 UTC | 2009-11-16 18:36:37 UTC | false |
| 6  | red   | 2009-11-16 18:36:37 UTC | 2009-11-16 19:15:18 UTC | true  |
+----+-------+-------------------------+-------------------------+-------+
2 rows in set
>> red_proxy.clean
  Shirt Load (0.4ms)   SELECT * FROM "shirts" WHERE (("shirts"."clean" = 't' AND "shirts"."color" = 'red'))
+----+-------+-------------------------+-------------------------+-------+
| id | color | created_at              | updated_at              | clean |
+----+-------+-------------------------+-------------------------+-------+
| 6  | red   | 2009-11-16 18:36:37 UTC | 2009-11-16 19:15:18 UTC | true  |
+----+-------+-------------------------+-------------------------+-------+
1 row in set
>>

So, even though I can compose named scopes, I can’t defer the query to the point I define which operation to execute. I’m obviously not the first one who had this problem, but it seems like this won’t be fixed until a new solution is proposed in Rails 3.

In our particular case, we didn’t even need the proxy object, but just a hash representation of the composed query parameters. We’re using ActiveRecord::Extensions (ar-extensions) to perform some UNION queries to aggregate results, and it expects a list of hashes that represent each query. It would be nice if I could grab the hash from my named scopes without performing the query…

The documentation points to a proxy_options method that you can use to test your named scope, but it didn’t solve my problem, since it only returns the hash for the last scope in your chain:

>> Shirt.red.proxy_options
=> {:conditions=>{:color=>"red"}}
>> Shirt.red.clean.proxy_options
=> {:conditions=>{:clean=>true}}

The solution, after digging a while through the source code, was to use an internal method that’s used during the merge algorithm, called current_scoped_methods:

>> Shirt.red.clean.current_scoped_methods[:find]
=> {:conditions=>"("shirts"."clean" = 't' AND "shirts"."color" = 'red')"}

This is not a perfect solution, but saved me the trouble of performing the merge myself. I hope the Rails 3 solution addresses these issues and that this post saves me some time digging through ActiveRecord source code if I need this before then :)

Post to Twitter