Comment faire une requête LIKE dans Arel et Rails?

115

Je veux faire quelque chose comme:

SELECT * FROM USER WHERE NAME LIKE '%Smith%';

Ma tentative à Arel:

# params[:query] = 'Smith'
User.where("name like '%?%'", params[:query]).to_sql

Cependant, cela devient:

SELECT * FROM USER WHERE NAME LIKE '%'Smith'%';

Arel encapsule correctement la chaîne de requête «Smith», mais comme il s'agit d'une instruction LIKE, cela ne fonctionne pas.

Comment faire une requête LIKE dans Arel?

Bonus PS - J'essaie en fait de scanner deux champs de la table, le nom et la description, pour voir s'il y a des correspondances avec la requête. Comment cela fonctionnerait-il?

filsa
la source
1
J'ai mis à jour la réponse arel pour le bonus.
Pedro Rolo

Réponses:

275

Voici comment vous effectuez une requête similaire dans arel:

users = User.arel_table
User.where(users[:name].matches("%#{user_name}%"))

PS:

users = User.arel_table
query_string = "%#{params[query]}%"
param_matches_string =  ->(param){ 
  users[param].matches(query_string) 
} 
User.where(param_matches_string.(:name)\
                       .or(param_matches_string.(:description)))
Pedro Rolo
la source
10
Contrairement à l'utilisation where("name like ?", ...), cette approche est plus portable dans différentes bases de données. Par exemple, cela entraînerait ILIKEune utilisation dans une requête sur une base de données Postgres.
dkobozev
20
est-ce protégé contre les injections SQL?
sren
7
Cela ne protège PAS complètement contre l'injection SQL. Essayez de définir user_name sur "%". La requête renverra des correspondances
travis-146
5
J'ai essayé d'injecter SQL en utilisant directement les paramètres User.where(users[:name].matches("%#{params[:user_name]}%")), j'ai essayé TRUNCATE users;et d'autres requêtes similaires et rien ne s'est passé du côté SQL. Cela me semble sûr.
earlonrails
5
À utiliser .gsub(/[%_]/, '\\\\\0')pour échapper les caractères génériques MySql.
aercolino
116

Essayer

User.where("name like ?", "%#{params[:query]}%").to_sql

PS.

q = "%#{params[:query]}%"
User.where("name like ? or description like ?", q, q).to_sql

Et ça fait longtemps mais @ cgg5207 a ajouté une modification (surtout utile si vous allez rechercher des paramètres à nom long ou plusieurs paramètres à nom long ou si vous êtes trop paresseux pour taper)

q = "%#{params[:query]}%"
User.where("name like :q or description like :q", :q => q).to_sql

ou

User.where("name like :q or description like :q", :q => "%#{params[:query]}%").to_sql
Reuben Mallaby
la source
9
Comment Rails sait-il qu'il ne doit pas s'échapper %dans la chaîne substituée? Il semble que si vous ne vouliez qu'un caractère générique unilatéral, rien n'empêche l'utilisateur de soumettre une valeur de requête qui inclut %aux deux extrémités (je sais qu'en pratique, Rails empêche %d'apparaître dans une chaîne de requête, mais il semble y avoir devrait être une protection contre cela au niveau ActiveRecord).
Steven
8
N'est-ce pas vulnérable aux attaques par injection SQL?
Behrang Saeedzadeh
7
@Behrang no 8) User.where ("nom comme% # {params [: query]}% ou description comme% # {params [: query]}%"). To_sql serait vulnérable, mais, dans le format que je montre , Rails échappe les paramètres [: query]
Reuben Mallaby
Désolé pour offtopic. J'ai le sql git de method to_sqlou arel manager, comment exécuter le sql sur db?
Малъ Скрылевъ
Model.where (to_sql_result)
Pedro Rolo
4

La réponse de Reuben Mallaby peut être raccourcie pour utiliser les liaisons de paramètres:

User.where("name like :kw or description like :kw", :kw=>"%#{params[:query]}%").to_sql
cgg5207
la source