Rails find :conditions

April 20th, 2007

The Rails application I'm currently working on accepts URLs like:

GET /submarks
GET /submarks?lat=:latitude&lng=:longitude
GET /submarks?lat=:latitude&lng=:longitude&user=:user_id
GET /submarks?lat=:latitude&lng=:longitude&channel=:channel_id
GET /submarks?lat=:latitude&lng=:longitude&channel=:channel_id&user=:user_id

All query parameters are optional, only lat and lng have to appear together. On the Rails end, I have a Submark model with the attributes: lat, lng, user_id and channel_id.

So how do you implement find call on the Submark model without a long, ugly if-elsif-else statements? First of all if you only have = as comparators you can use a Hash for the :conditions parameter of the find method:

conditions = {}
conditions[:user_id] => params[:user] if params[:user]
conditions[:channel_id] => params[:channel] if params[:channel]
Submark.find(:all, :conditions => conditions)

But what if you want OR instead of AND in the WHERE clause? Or what if you want do something like lat < 16? Currently, you have revert back to ugly if-elsif-else statements like:

if params[:user] and params[:channel].nil?
  Submark.find(:all, :conditions => ["user_id = ?", params[:user]])
elsif params[:user].nil? and params[:channel]
  Submark.find(:all, :conditions => ["user_id = ?", params[:user]])
elsif params[:user] and params[:channel]
  Submark.find(:all, :conditions => ["user_id = ? AND channel_id = ?", params[:user], params[:channel]])
else
  Submark.find(:all)
end

A good solution for this problem is the ez_where plugin by Ezra Zygmuntowicz and Fabien Franzen. It includes new find_where method in your ActiveRecord models which takes a code block building the conditions with a quite nifty syntax.

But I didn't want to depend on a external plugin, mainly because we are working with edge Rails, so it's not unlikely that ez_where breaks sometime during development.

Instead I've implemented a little helper, create_conditions, which is able to construct a conditions array, in the format the usualfind method likes. It also works by utilizing a code block. You get a Conditions class as parameter to your code block, which has the methods and and or.

This is how you would use create_conditions in your controller:

conditions = create_conditions do | c |
  c.and ["channel_id = ?", params[:channel]] if params[:channel]
  c.and ["user_id = ?", params[:user]] if params[:user]
  c.or "distance < 10"
end
Submark.find(:all, :conditions => conditions)

The result of create_conditions looks like:

["channel_id = ? AND user_id = ? OR distance < 10", 1, 42]

As you can see, you can use both, String or Array based conditions. Hash based conditions are not supported.

Get the code here.

6 Responses to “Rails find :conditions”

  1. kain Says:
    hi there, very useful plugin, thanks. however, it would be nice to provide a method not to write a condition if none is met, eg:
    
        conditions = create_conditions do |c|
          c.and ["products.company_id = ?", this_user.id] if this_user.is_a_company?
        end
    
    
    this will throw an invalid sql syntax, because nothing is satisfied.
  2. Steve Says:
    That's really easy to solve. Just do this: conditions = create_conditions do |c| c.and ["1=1"] c.and ["products.company_id = ?", this_user.id] if this_user.is_a_company? end
  3. Steve Says:
    Nope, sorry, that code doesn't work when other conditions are passed. Instead, do this: conditions = create_conditions do |c| c.and ["1=?", 1] c.and ["products.company_id = ?", this_user.id] if this_user.is_a_company? end
  4. phil Says:
    Excellent, that's exactly what I need... Learning is fun!
  5. claudemiro Says:
    hello. how can i put this helper visible for my controllers? thanks
  6. thoraxe Says:
    This is so insanely much faster than what I was doing. I have a horrendous number of associations and this enabled me to handle everything in the find instead of rejecting things, and it was easy to code. great job.

Leave a Reply