Rails find :conditions
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 usual find 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.
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:
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.
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
Excellent, that’s exactly what I need… Learning is fun!
hello. how can i put this helper visible for my controllers? thanks
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.
excellent helper method. it cleaned up my code a lot.
It works. Thanks!