views:

221

answers:

2

Let's say I have a form where users can search for people whose name starts with a particular name string, for example, "Mi" would find "Mike" and "Miguel". I would probably create a find statement like so:

find(:all, :conditions => ['name LIKE ?', "#{name}%"])

Let's say the form also has two optional fields, hair_color and eye_color that can be used to further filter the results. Ignoring the name portion of the query, a find statement for people that can take in an arbitrary number of optional parameters might look like this:

find(:all, :conditions => { params[:person] })

Which for my two optional parameters would behave as the equivalent of this:

find(:all, :conditions => { :hair_color => hair_color, :eye_color => eye_color })

What I can't figure out is how to merge these two kinds of queries where the required field, "name" is applied to the "like" condition above, and the optional hair_color and eye_color parameters (and perhaps others) can be added to further filter the results.

I certainly can build up a query string to do this, but I feel there must be a "rails way" that is more elegant. How can I merge mandatory bind parameters with optional parameters?

+2  A: 

This is the perfect use of a named scope.

create a named scope in the model:

named_scope :with_name_like, lambda {|name|
  {:conditions => ['name LIKE ?', "#{name}%"]}
}

At this point you can call

Model.with_name_like("Mi").find(:all, :conditions => params[:person])

And Rails will merge the queries for you.

Edit: Code for Waseem:

If the name is optional you could either omit the named scope from your method chain with an if condition:

unless name.blank?
   Model.with_name_like("Mi").find(:all, :conditions => params[:person])
else
   Model.find(:all, :conditions => params[:person])
end

Or you could redefine the named scope to do the same thing.

named_scope :with_name_like, lambda {|name|
  if name.blank?
    {}
  else
    {:conditions => ['name LIKE ?', "#{name}%"]}
  end
}
EmFi
Just a curiosity. How would you extend it if specifying the name string was also optional?
Waseem
I knew there had to be a nice clean way to do it. There always is with rails :-) Thanks!
SingleShot
@waseem, I've amended the solution to answer your question.
EmFi
Update: I watched a Railscast on the Seachlogic gem - what a great gem - basically does everything above with much more flexibility and without having to write your own named scopes.
SingleShot
A: 

To comply also with Waseem request, but allowing nil instead blank? (which is udeful in case you want to use "@things = Thing.named_like(params[:name])" directly)

named_scope :named_like, lambda do |*args| 
  if (name=args.first)
    {:conditions => ["name like ?",name]}
  else
    {}
  end
end

# or oneliner version:

named_scope :named_like, lambda{|*args| (name=args.first ? {:conditions => ["name like ?",name]} : {}) } }

I hope it helps

Fer