Consulta Rails 4 LIKE - ActiveRecord adiciona aspas

127

Eu estou tentando fazer uma consulta como assim

def self.search(search, page = 1 )
  paginate :per_page => 5, :page => page,
    :conditions => ["name LIKE '%?%' OR postal_code like '%?%'", search, search],   order => 'name'
end

Mas quando é executado, algo está adicionando aspas, o que faz com que a instrução sql saia dessa maneira

SELECT COUNT(*)
FROM "schools" 
WHERE (name LIKE '%'havard'%' OR postal_code like '%'havard'%')):

Então você pode ver o meu problema. Estou usando o Rails 4 e o Postgres 9, os quais nunca usei, portanto, não tenho certeza se isso é algo de um registro ativo ou possivelmente algo do postgres.

Como posso configurar isso para que eu tenha como '%my_search%'na consulta final?

Harry Forbess
fonte

Respostas:

228

Seu espaço reservado é substituído por uma sequência e você não está manipulando direito.

Substituir

"name LIKE '%?%' OR postal_code LIKE '%?%'", search, search

com

"name LIKE ? OR postal_code LIKE ?", "%#{search}%", "%#{search}%"
rb512
fonte
6
Isso não é vulnerável à injeção de SQL? Quero dizer, essas searchcordas são higienizadas?
jdscosta91
6
@ jdscosta91 o ?no qual vai cuidar de saneantes
house9
7
As instâncias @ house9 %e _inside searchnão serão higienizadas, sob essa abordagem.
Barry Kelly
8
Ao usar '?' dessa maneira, em trilhos, é convertido em uma consulta parametrizada. Os dados no parâmetro não são limpos (%), mas é impossível mudar o contexto da consulta e transformá-lo em instruções SQL processadas.
David Hoelzer
50

Em vez de usar a conditionssintaxe do Rails 2, use o wheremétodo do Rails 4 :

def self.search(search, page = 1 )
  wildcard_search = "%#{search}%"

  where("name ILIKE :search OR postal_code LIKE :search", search: wildcard_search)
    .page(page)
    .per_page(5)
end

NOTA: o acima usa sintaxe de parâmetro em vez de? espaço reservado: esses dois devem gerar o mesmo sql.

def self.search(search, page = 1 )
  wildcard_search = "%#{search}%"

  where("name ILIKE ? OR postal_code LIKE ?", wildcard_search, wildcard_search)
    .page(page)
    .per_page(5)
end

NOTA: usando ILIKEpara o nome - versão sem diferenciação de maiúsculas e minúsculas do LIKE

house9
fonte
Isso ainda é válido para o Rails 5? Porque se eu tiver Movie.where("title ILIKE :s", s: search_string)isso é traduzido para SELECT 1 AS one FROM "movies" WHERE (title ILIKE 'test') LIMIT $1pelo ActiveRecord (Rails 5.1.6) - aviso por favor que não há nenhum símbolo de porcentagem após a ILIKE)
sekmo
@ sekmo - deve funcionar, a porcentagem iria na search_stringvariável. Eu acho que a SELECT 1 AS onesaída está apenas no console de trilhos ou você está usando limit(1)? FYI: Rails 5.1.6 tem problemas de segurança, use 5.1.6.2 ou 5.1.7 vez
house9
22

Embora a interpolação de strings funcione, como sua pergunta especifica os trilhos 4, você pode usar o Arel para isso e manter o banco de dados de aplicativos independente.

def self.search(query, page=1)
  query = "%#{query}%"
  name_match = arel_table[:name].matches(query)
  postal_match = arel_table[:postal_code].matches(query)
  where(name_match.or(postal_match)).page(page).per_page(5)
end
numbers1311407
fonte
Deve ser realmente fácil de criar um func fazer isso dinamicamente com uma lista de atributos
cevaris
Não testado, embora possa ser algo como isto: escopo search_attributes, -> (consulta, atributos) {arel_attributes = attribute.map {| a | arel_table [a]} arel_queries = arel_attributes.map {| a | a.matches (query)} return onde (arel_queries.reduce {| res, q | res.or q})}
Pedro Rolo
8

O ActiveRecord é inteligente o suficiente para saber que o parâmetro referido por ?é uma string e, portanto, o coloca entre aspas simples. Como um post sugere, você pode usar a interpolação de string Ruby para preencher a string com os %símbolos necessários . No entanto, isso pode expô-lo à injeção de SQL (que é ruim). Eu sugiro que você use a CONCAT()função SQL para preparar a string da seguinte maneira:

"name LIKE CONCAT('%',?,'%') OR postal_code LIKE CONCAT('%',?,'%')", search, search)

John Cleary
fonte
Acabei de implementar isso em um aplicativo e funcionou muito bem. Obrigado John.
Fuzzygroup
em relação às injeções, não, não e, de qualquer forma, isso não faz diferença. A string é inserida no sql e os trilhos deveriam tê-la validado antes (não importa se a %é anexado / anexado ou não). Funciona como esperado ou os trilhos têm um erro grave que afeta os dois casos.
estani 24/01
5

Experimentar

 def self.search(search, page = 1 )
    paginate :per_page => 5, :page => page,
      :conditions => ["name LIKE  ? OR postal_code like ?", "%#{search}%","%#{search}%"],   order => 'name'
  end

Consulte os documentos sobre condições AREL para obter mais informações.

tihom
fonte
-1
.find(:all, where: "value LIKE product_%", params: { limit: 20, page: 1 })
Jeff
fonte