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.
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' ) |
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.
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.
Sorry, comments are closed for this article.
February 1st, 2006 at 09:22 PM 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.
February 2nd, 2006 at 02:10 AM I have the same experience with both Ruby and Rails on a regular basis.