L-exp Mobile

investigating named_scope

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:

1 class Cart < ActiveRecord::Base 2 named_scope :placed, {:conditions => ["status IN (?)", ["Ordered", "Closed"]} 3 named_scope :since, lambda{|date| {:conditions => ["created_at >= ?", date]} } 4 end

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 end

And 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 end

Options:   Save This | Share
Viewed 0 times
Published 3 months ago
By dpalm
From Resource Elc Tech Blog in lists:
Best Ruby on Rails Blogs

Menu

by Genís