This morning I spent quite a while going through the source code that implements named_scopes in ActiveRecord 2.1. The code is short and kind of interesting in itself, so give it a read if you're interested! Kudos to rails-core for getting this in.
What I wanted to achieve is being able to write code like:
1 milk = Product.find_by_name('milk') 2 customer.orders.placed.since(1.week.ago).for(milk)Using vanilla named_scopes works fine for most of the above, but I ran into a gotcha when I tried to add a non-database-based named scope.
I had something like this:
With that in place I can do:
1 customer = Customer.first 2 customer.orders.placed.since(1.week.ago)Sweet.
Now I needed:
1 beef = Product.find(123) 2 customer.orders.placed.since(1.week.ago).for(beef)If "for" could have been solved by some SQL I could have added it as another bunch of conditions. In my case it's not that simple though. I need some ruby code in there to check what product a given order was for.
You can extend named_scopes just like with has_many associations, so:
1 class Cart < ActiveRecord::Base 2 named_scope :placed, {:conditions => ["status IN (?)", ["Ordered", "Closed"]} 3 named_scope :since, lambda{|date| {:conditions => ["created_at >= ?", date]} } do 4 def for(product) 5 # stuff 6 end 7 end 8 endAnd here's the gotcha: the scope inside for() changes if you call the named_scope and the :extended method from the class or from a has_many association, i.e.:
1 Order.placed.since(1.week.ago).for(beef)doesn't give you the same "self" as:
1 customer.orders.placed.since(1.week.ago).for(beef)In actuallity, it's not "self" that is different, but the "proxy_scope". In the former case proxy_scope is the Order class, in the latter it's the Array of orders placed by our customer.
Not what I expected.
:-(To make it work for both I had to do:
1 collection = proxy_scope.is_a?(Array) ? proxy_scope : self.send(:load_found)('load_found' is a private instance method on ActiveRecord::NamedScope::Scope)
Not so pretty, but working.
Final version:
1 class Cart < ActiveRecord::Base 2 named_scope :placed, {:conditions => ["status IN (?)", ["Ordered", "Closed"]} 3 named_scope :since, lambda{|date| {:conditions => ["created_at >= ?", date]} } do 4 def for(product) 5 collection = proxy_scope.is_a?(Array) ? proxy_scope : self.send(:load_found) 6 collection.reject do |order| 7 # crazy stuff to find out if it's in our out 8 end 9 end 10 end 11 endby Genís