Why you should not interpolate conditions strings directly in Rails
Posted in Uncategorized on April 29th, 2010 by Marcelo de Moraes Serpa – Be the first to commentThe situation is the following: you have a condition string that you built and, let’s say, to implement a simple search feature on some attributes of a model. To keep things simple, let’s say you have something like:
cond = "lower(name) LIKE '%#{s}%'"
Model.find(:all,:conditions => cond)
Do not do that. Don’t interpolate strings directly into the condition like this. Here’s why:
1) You are doing something Rails is supposed to do for you;
2) It will generate a non-sense query if your search string contains any string in that matches the following regexp:
/([\.a-zA-Z_]+).?\./
Or, in other words, any string like “x.z”, where x and z are also strings and z is optional.
I found about this when I was searching for a model that had a name with a dot (a domain), and postgreSQL was throwing an ambiguous query error. After some minutes digging through the ActiveRecord (2.3.5) codebase, I found the root of the problem on The problem lies on lib/active_record/associations.rb#tables_in_string. This method tries to find additional tables to join in by looking for patterns such as users.name, for example (where users is the table name extracted). However, anything that follows this pattern will be also be considered to be the name of a table, and obviously will wreak havoc the query.
At first, I thought it was an ActiveRecord bug. It indeed could be considered a bug, but after some moments meditating about the subject, I figured out *I* was going ahead of Rails and doing stuff it is supposed to do. So, the way to pass conditions is to always wrap it inside an array, like so:
s = "%searchstring%"
cond = ["lower(name) LIKE ?",?]
Model.find(:all,:conditions => cond)
This way, when this string reaches the tables_in_string method, it won’t have the search string interpolated (yet), and it won’t break the query.
