Better Rails Searching with Named Scopes using Scope Builder
Posted: December 22nd, 2008 | Author: gabe | Filed under: Rails | Tags: named scope, Rails, search | No Comments »When it comes writing elegant search code in a Rails app, Named Scopes immediately come to mind. And for good reason: they’re a fantastic way to express, well, scopes, for your searches. In your Person model, you might have named scopes like by_last_name, by_age, and by_sex. But, what do you do when you want to give your users a search form and let them find people by any combination of last name, age, and sex?
Well, you might try something like executing each named scope that you have params for and find the intersection of the IDs of all people returned from each named_scope. That might look something like this:
p1 = Person.by_last_name params[:last_name] if params[:last_name] p2 = Person.by_age params[:age] if params[:age] p3 = Person.by_sex params[:sex] if params[:sex] people += p1 if p1.length + p2 + p3
What you really want is Ryan Bates’ Scope Builder gem.
Scope Builder lets you “build up named scopes conditionally.” From the README:
This gem adds the scope_builder method to all Active Record models. A
builder behaves exactly like any other named scope except that calling
other named scopes on it will alter the builder itself rather than
returning a new named scope.
Here’s some example code using that defines Person.search using named scopes for last_name, age, and sex:
class Person < ActiveRecord::Base named_scope :by_last_name, lambda { |last_name| { :conditions => ['last_name = :last_name', { :last_name => last_name } ] } } named_scope :by_age, lambda { |age| { :conditions => ['age = :age', { :age => age } ] } } named_scope :by_sex, lambda { |sex| { :conditions => ['sex = :sex', { :sex => sex } ] } } def self.search(options) scope_builder do |builder| builder.by_last_name(options[:last_name]) if options[:last_name] builder.by_age(options[:age]) if options[:age] builder.by_sex(options[:sex]) if options[:sex] end end
So, now all you need is a People Search form and an action to handle finding the right people. Here’s what your search action might look like inside PeopleController:
def search @people = Person.search(params).all end
Important note: When using ScopeBuilder as shown above, you’ll have to call #all because, in this case, Person.search returns a ScopeBuilder object, not a collection of ActiveRecord Person objects. To get at the people collection, you call #all on the ScopeBuilder object returned by Person.search.
So, see? Isn’t that pretty? You’ve got a simple search that will find all the people that meet any all of the attributes passed in from the search form. Ta da!
Leave a Reply