Question

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?

Was it helpful?

Solution

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
}

Update

Here is the Rails 3 version of the last code snippet:

scope :with_name_like, lambda {|name|
  if not name.blank?
    where('name LIKE ?', "#{name}%")
  end
}

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top