NOVEMBER 15, 2009
Methods with_scope and with_exclusive_scope in ActiveRecord scope parameters to method calls within the block. They can be nested, and when with_scope blocks are nested, the scope of the inner block is the merge of all the parent scopes:
class Task < ActiveRecord::Base
class << self
def foo
find(:all) # no conditions
with_scope(:find => { :conditions => "priority_id is null" }) do
find(:all) # :conditions => "priority_id is null"
with_scope(:find => { :conditions => "relevant_version_id is null" }) do
find(:all) # :conditions => "priority_id is null and relevant_version_id is null"
end
find(:all) # back to :conditions => "priority_id is null"
end
end
end
endwith_exclusive_scope doesn't merge scope with the parent blocks:
def foo
find(:all) # no conditions
with_scope(:find => { :conditions => "priority_id is null" }) do
find(:all) # :conditions => "priority_id is null"
with_scope(:find => { :conditions => "relevant_version_id is null" }) do
find(:all) # :conditions => "priority_id is null and relevant_version_id is null"
with_exclusive_scope(:find => { :conditions => "relevant_version_id is not null" }) do
find(:all) # :conditions => "relevant_version_id is not null"
end
end
find(:all) # back to :conditions => "priority_id is null"
end
endIf you look in the source code of with_scope, you will see how simple and elegant it really is, making use of the power of Ruby blocks.
Essentially, an ActiveRecord class object has a stack called scoped_methods, before yield a new scope structure is pushed to it, after the block is done executing, the last element is popped:
self.scoped_methods << method_scoping
begin
yield
ensure
self.scoped_methods.pop
endIn case of with_scope method_scoping is the merge of all previous elements in scoped_methods, in case of with_exclusive_scope method_scoping equals to the first argument to with_exclusive_scope.
This is how it works:
def foo
p self.scoped_methods #=> []
with_scope(:find => { :conditions => "priority_id is null" }) do
p self.scoped_methods #=> [{:find=>{:conditions=>"priority_id is null"}}]
with_scope(:find => { :conditions => "relevant_version_id is null" }) do
p self.scoped_methods #=> [{:find=>{:conditions=>"priority_id is null"}},
# {:find=>{:conditions=>"(priority_id is null) AND (relevant_version_id is null)"}}]
with_exclusive_scope(:find => { :conditions => "relevant_version_id is not null" }) do
p self.scoped_methods #=> [{:find=>{:conditions=>"priority_id is null"}},
# {:find=>{:conditions=>"(priority_id is null) AND (relevant_version_id is null)"}},
# {:find=>{:conditions=>"relevant_version_id is not null"}}]
end
with_exclusive_scope(:find => { }) do
p self.scoped_methods #=> [{:find=>{:conditions=>"priority_id is null"}},
# {:find=>{:conditions=>"(priority_id is null) AND (relevant_version_id is null)"}},
# {:find=>{}}]
end
end
end
endUntil recently WiceGrid was ignoring with_exclusive_scope and with_scope completely. The reason for this is the lazy nature of WiceGrid - the real ActiveRecord#find call doesn't happen in initialize_grid, but later. Beginning with this commit WiceGrid supports with_exclusive_scope and with_scope. Knowing how with_scope works the solution is dead simple - remember the last element in scoped_methods, when in initialize_grid, and later run ActiveRecord#find from with with_exclusive_scope block, submitting the remembered method scope to with_exclusive_scope.
Happy Rubying :-) !