Politics, Programming and Possibilities
25 Oct
So I’ve tried Ezra’s “ez_where” and InVisible’s “where” plugins, but in the end I just couldn’t get past the hackish feeling I got when I used them.
I liked the concept that Ezra built—trying to make a domain-specific language for a where clause in Ruby—but the result felt like a heavy-weight addition to a small but common problem. In addition, I had trouble memorizing which Ruby operators mapped to which SQL operators in fringe cases.
Using InVisible’s “where” plugin was a bit of a relief, as it was lighter weight and the mapping was more direct; however, in order to test for a condition in the case where the column is in another table, I had to resort to a “send” method, like this:
c = InVisible::Cond.new do
send("permissions.value", params[:role])
end
Which isn’t all that bad, but the inelegant solution gnawed at me for a while. Finally, I decided to make a “simplest-case” Condition class by re-using Ezra’s Cond class. The final outcome looks like this, and feels more Rubyesque to me:
@invitees = @book.permitted_users \
:conditions => Condition.block { |c|
c << [ "permissions.value", params[:role] ] if params[:role]
}
As with the InVisible::Cond class, you can do things like this:
cond = Condition.new do |c|
c << [ "first_name", "like", "#{prefix}%" ] if prefix
c << [ "verified", true ]
c << [ "created_at", ">", 5.days.ago ]
end
cond.where
# => ["first_name like ? AND verified = ? AND created_at > ?", "dua%", true, "2006-10-18"]
users = User.find(:all, :conditions => cond.where)
My simple version of the plugin is available for download as a tar/gz file here.
Thanks to both Ezra and InVisible for their pioneer work!
9 Responses for "Simplified Condition Class"
You might want to check out http://rubyforge.org/projects/arext/
It allows you to pass a hash to the conditions parameter
Book.find :all, :conditions => { :first_name_like =>
“#{prefix}”, :verified => true, :created_at_gt => 5.days.ago }
There are many other options you can use for it as well. Check it out!
Thanks, Mark. Do you have any further examples of how this plugin would be used? In particular, how would you use it in the case of several optional “filter parameters” passed in to the controller? For example:
Condition.new do |c|
c < < ["verified", true] if params[:verified]
c << ["name", "like", "%#{params[:name]}%"] if params[:name]
c << ["created_at", ">=”, params[:days_ago].to_i.days.ago if params[:days_ago]
end
Take a look here:
http://www.continuousthinking.com/ARE/better-finders
If you want things conditinoally in there, then you can simply build the bas before the find call.
Duane,
I remember we talked about this before. I used to think yearn for a plugin, I actually wrote one that just allows you to call find like this:
Object.find_with_conditions(…) do |cond|
cond.add(’first_name like ?’, params[:first_name]) unless params[:first_name].blank?
end
But lately I don’t use it. It feels like bloat ANY way you do it, since the code to do it manually is pretty simple:
['first_name', 'last_name', 'city'].each do |field|
unless params[field].blank?
conditions [conditions.join(' and '), *parameters])
This allows infinite flexibility and no bloat.
Duane,
I remember we talked about this before. I used to think yearn for a plugin, I actually wrote one that just allows you to call find like this:
Object.find_with_conditions(…) do |cond|
cond.add(’first_name like ?’, params[:first_name]) unless params[:first_name].blank?
end
But lately I don’t use it. It feels like bloat ANY way you do it, since the code to do it manually is pretty simple:
['first_name', 'last_name', 'city'].each do |field|
unless params[field].blank?
conditions << “(#{field} like ?)”
parameters << params[field] + ‘%’
end
end
Object.find(…, :conditions => [conditions.join(' and '), *parameters])
This allows infinite flexibility and no bloat.
What about conditions? Maybe you could do away with only pushing items on the condition stack and use #and #or.
An extension of this would be to allow #and and #or to take blocks to determine the scopee of the condition
eg:
cond = Condition.new do |c|
c “, 5.days.ago ]
sub
hmm, that code didn’t quite turn out right. Let me try again:
First a precautionary pastie link: http://p.caboo.se/20041
cond = Condition.new do |c|
c ", 5.days.ago ]
sub.and [ "verified", false ]
end
end
cond.where
# => ["first_name like ? AND verified = ? OR ( created_at > ? AND verified = ?)", "dua%", true, "2006-10-18", false]
PS: comment preview would be nice
Thanks, Lee and Jeff. I think there’s merit to your no-bloat approach, Jeff. Maybe I will forever remain unsatisfied with either solution
Regarding your suggestion Lee, I’ve seen that done somewhere else before, and I like the concept. It adds a nice Rubyesque feel to the conditions. If a need pops up, I’ll definitely add that in there.
Hi Duane Johnson.
thanks for your work.
Is there also a chance to do something like that:
Student.find(:all, :conditions => { :grade => (9..12) })
# => SELECT * FROM students WHERE grade BETWEEN 9 and 12.
Regards Werner
Leave a reply