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.
June 12th, 2007 at 10:00 PM 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: this will throw an invalid sql syntax, because nothing is satisfied.
September 11th, 2007 at 04:31 AM 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
September 11th, 2007 at 04:39 AM 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? endApril 11th, 2008 at 11:03 AM Excellent, that's exactly what I need... Learning is fun!
April 22nd, 2008 at 08:59 PM hello. how can i put this helper visible for my controllers? thanks
May 15th, 2008 at 04:11 PM 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.