November 16th, 2009Merging named scopes without performing a query
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 :)















