Como fazer uma consulta LIKE no Arel e Rails?

115

Eu quero fazer algo como:

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

Minha tentativa em Arel:

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

No entanto, isso se torna:

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

Arel envolve a string de consulta 'Smith' corretamente, mas como esta é uma instrução LIKE, ela não funciona.

Como alguém faz uma consulta LIKE no Arel?

Bônus PS - Na verdade, estou tentando verificar dois campos da tabela, nome e descrição, para ver se há alguma correspondência com a consulta. Como isso funcionaria?

filsa
fonte
1
Atualizei a resposta arel para o bônus.
Pedro Rolo

Respostas:

275

É assim que você executa uma consulta semelhante no 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
fonte
10
Ao contrário do uso where("name like ?", ...), essa abordagem é mais portátil em bancos de dados diferentes. Por exemplo, isso resultaria em ILIKEser usado em uma consulta em um banco de dados Postgres.
dkobozev
20
isso está protegido contra injeções de SQL?
sren
7
Isso NÃO protege totalmente contra injeção de SQL. Tente definir user_name para "%". A consulta retornará correspondências
travis-146
5
Tentei injetar sql usando params diretamente, User.where(users[:name].matches("%#{params[:user_name]}%"))tentei TRUNCATE users;e outras consultas semelhantes e nada aconteceu no lado do sql. Parece seguro para mim.
Earonrails
5
Use .gsub(/[%_]/, '\\\\\0')para escapar de caracteres curinga do MySql.
aercolino
116

Experimentar

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

PS.

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

Aaand já faz muito tempo, mas @ cgg5207 adicionou uma modificação (principalmente útil se você for pesquisar parâmetros de nomes longos ou múltiplos ou se você estiver com preguiça de digitar)

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
fonte
9
Como o Rails sabe que não deve escapar %na string substituída? Parece que se você só quisesse um caractere curinga unilateral, não há nada que impeça o usuário de enviar um valor de consulta que inclua %em ambas as extremidades (eu sei que na prática, Rails impede %de aparecer em uma string de consulta, mas parece que existe deve haver proteção contra isso no nível ActiveRecord).
Steven
8
Isso não é vulnerável a ataques de injeção de SQL?
Behrang Saeedzadeh
7
@Behrang no 8) User.where ("nome como% # {params [: query]}% ou descrição como% # {params [: query]}%"). To_sql seria vulnerável, mas, no formato que mostro , Rails escapa dos parâmetros [: query]
Reuben Mallaby
Desculpe por offtopic. Eu tenho o sql git do método to_sqlou gerenciador arel, como executar o sql no banco de dados?
Малъ Скрылевъ
Model.where (to_sql_result)
Pedro Rolo
4

A resposta de Reuben Mallaby pode ser encurtada ainda mais para usar ligações de parâmetro:

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