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 statement? First of all, if you only have = as a comparator 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 to do something like lat < 16? Currently, you have revert 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 a 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 an external plugin, mainly because, as we’re working with edge Rails, it’s not unlikely that ez_where will break 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- and Array-based conditions. Hash-based conditions are not supported.
comments powered by Disqus