Using with_scope to refactor Rails finder methods.

Background

I have come across a great way of refactoring finder methods, yet still maintaining the flexibilty of the built in ActiveRecord finder methods.

First I'll show an inflexible way of refactoring a simple finder. I was calling the following method from several places in my controller:

1
2
3
4

@emps = Employee.find(:all, 
                      :conditions => "work_status <> 'Gone'"
                     )

Creating a static finder in my Employee class makes calling this method a bit more simple.

1
2
3
4
5
6
7
8

class Employee < ActiveRecord::Base
  def self.find_all_current
    find(:all, 
         :conditions => "work_status <> 'Gone'"
        )
  end
end

Now I can call:

1
2

Employee.find_all_current

Finding employees that aren't "Gone" will be a common task throughout the application and it would be nice to have some flexibility in finding them. I'd hate to have to create another static finder method like just to order the employees differently or add another simple condition.

Utilizing with_scope

The best way to remove this duplication is to use with_scope. with_scope allows finder methods to be called normally, but have conditions applied to find or create methods called from within the block. An example will clarify this concept:

1
2
3
4
5
6

Employee.with_scope(:find => { :conditions => "work_status <> 'Gone'" } ) do
    Employee.find(1) # => SELECT * from employees 
                     #    WHERE work_status <> 'Gone' 
                     #    AND id = 1
end

Now we can apply this concept to help make the refactored finder method a lot more flexible:

1
2
3
4
5
6
7
8

class Employee < ActiveRecord::Base
  def self.find_current(*args)
    with_scope(:find => { :conditions => "work_status <> 'Gone'" }) do
      find(*args) 
    end
  end
end

This allows us to find all current employees, but with the flexibility to specify more parameters. A few examples are:

1
2
3
4
5
6
7
8
9

@emp = Employee.find_current(:first)

@emps = Employee.find_current(:all)

@emps_named_bill = Employee.find_current(:all,
                                         :conditions => "name like '%bill%'",
                                         :order => 'last_name, first_name'
                                        )

Summary

As you can see from the above code we maintain all the power of ActiveRecord's finder methods while imposing a constraint of our own in a straight forward, legible manner. In addition, it would be in the spirit of DRY to create other static finders in terms of find_current, such as Employee.find_current_ordered. One current limitation is that nested scopes are not yet supported.

Update

Nested with_scope will be supported in Rails 1.1. To see the true potential of Rails ActiveRecord scoping check out this article on Nested with_scope by AnnaChan. It will rock your world.

2 Responses to “Using with_scope to refactor Rails finder methods.”

  1. Tristan Dunn Says:
    Excellent, excellent article. Ruby, and Rails as well, never ceases to amaze me. Right when you think you know a lot, something else comes along that blows you away. Thanks for the tip.
  2. Cody Fauser Says:
    I have the same experience with both Ruby and Rails on a regular basis.

Sorry, comments are closed for this article.