WiceGrid and with_scope

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
end

with_exclusive_scope doesn’t merge scope with the parent blocks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
end

If 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:

1
2
3
4
5
6
self.scoped_methods << method_scoping
begin
  yield
ensure
  self.scoped_methods.pop
end

In 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

end

Until 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 :-) !