Safe ActiveRecord like query
Solution 1
To ensure that your query string gets properly sanitized, use the array or the hash query syntax to describe your conditions:
Foo.where("bar LIKE ?", "%#{query}%")
or:
Foo.where("bar LIKE :query", query: "%#{query}%")
If it is possible that the query
might include the %
character and you do not want to allow it (this depends on your usecase) then you need to sanitize query
with sanitize_sql_like
first:
Foo.where("bar LIKE ?", "%#{sanitize_sql_like(query)}%")
Foo.where("bar LIKE :query", query: "%#{sanitize_sql_like(query)}%")
Solution 2
Using Arel you can perform this safe and portable query:
title = Model.arel_table[:title]
Model.where(title.matches("%#{query}%"))
Solution 3
For PostgreSQL it will be
Foo.where("bar ILIKE ?", "%#{query}%")
Solution 4
In case if anyone performing search query on nested association try this:
Model.joins(:association).where(
Association.arel_table[:attr1].matches("%#{query}%")
)
For multiple attributes try this:
Model.joins(:association).where(
AssociatedModelName.arel_table[:attr1].matches("%#{query}%")
.or(AssociatedModelName.arel_table[:attr2].matches("%#{query}%"))
.or(AssociatedModelName.arel_table[:attr3].matches("%#{query}%"))
)
Don't forget to replace AssociatedModelName
with your model name
Solution 5
You can do
MyModel.where(["title LIKE ?", "%#{params[:query]}%"])
Gal Weiss
Updated on July 13, 2022Comments
-
Gal Weiss almost 2 years
I'm trying to write LIKE query.
I read that pure string quires aren't safe, however I couldn't find any documentation that explain how to write safe LIKE Hash Query.
Is it possible? Should I manually defend against SQL Injection?
-
Andrew Moore about 7 yearsThis is the preferable solution, since Arel is sql-db-agnostic and has some internal input cleansing. Also is much more legible and consistent as far as code-style goes, IMHO.
-
Beni Cherniavsky-Paskin over 6 yearsThis fails to escape
%
in the query string. It's not arbitrary "SQL injection" but still may function unexpectedly. -
spickermann over 6 years@BeniCherniavsky-Paskin : That is the whole point, you do not want to escape the
%
because the%
is part of theLIKE
syntax. If you escaped the%
then the result would basically be a normal=
query. -
Beni Cherniavsky-Paskin over 6 yearsRight, YOU want to use % wildcards in your pattern template but that pattern is parametrized with
query
variable, and in many cases you want to match literally the string inquery
variable, not allowquery
to use LIKE metacharacters. Let's take a more realistic example that %...%: the strings have a path-like structure, and you try to match/users/#{user.name}/tags/%
. Now if I arrange my username to befr%d%
, I'll be able to observefred
andfrida
's tags... -
Beni Cherniavsky-Paskin over 6 yearsOK, what I'm after is combining this question with stackoverflow.com/questions/5709887/… which suggests
sanitize_sql_like()
. -
spickermann over 6 years@BeniCherniavsky-Paskin Now I understand where you a coming from and you are right. I updated my answer to address that issue.
-
Beni Cherniavsky-Paskin over 6 years👍 Worth also mentioning
_
and \ characters as needing escaping. -
Noach Magedman about 6 yearsHow do you negate this? (i.e. NOT LIKE)
Model.where(title.matches("%#{query}%").not)
works, although the generated SQL is a little awkward:WHERE (NOT (`models`.`title` LIKE '%foo%'))
-
Noach Magedman about 6 yearsAah...found it.
Model.where(title.does_not_match("%#{query}%"))
. Generates:WHERE (`models`.`title` NOT LIKE '%foo%')
-
Santhosh over 5 years@mikkeljuhl Please look at my answer carefully.
-
vjt almost 5 yearsCareful - this fails to sanitise
%
in untrusted input:>> ActiveRecord::VERSION::STRING => "5.2.3" >> field = Foo.arel_table[:bar] >> Foo.where(field.matches('%')).to_sql => "SELECT `foos`.* FROM `foos` WHERE `foos`.`bar` LIKE '%'"
-
elquimista over 4 years@NoachMagedman or
Model.where.not(title.matches("%#{query}%"))
.does_not_match
reads better though, IMO. -
sekrett about 3 yearsThe field has to be citext type to make case insensitive search work. Otherwise it will be the same as LIKE.