Requête Rails 4 LIKE - ActiveRecord ajoute des guillemets

127

J'essaye de faire une requête similaire comme ça

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

Mais quand il est exécuté, quelque chose ajoute des guillemets, ce qui provoque la sortie de l'instruction sql comme ceci

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

Vous pouvez donc voir mon problème. J'utilise Rails 4 et Postgres 9, tous deux que je n'ai jamais utilisés, donc je ne sais pas si c'est une chose d'activerecord ou éventuellement une chose postgres.

Comment puis-je configurer cela pour avoir comme '%my_search%'dans la requête finale?

Harry Forbess
la source

Réponses:

228

Votre espace réservé est remplacé par une chaîne et vous ne le gérez pas correctement.

Remplacer

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

avec

"name LIKE ? OR postal_code LIKE ?", "%#{search}%", "%#{search}%"
rb512
la source
6
N'est-ce pas vulnérable à l'injection SQL? Je veux dire, ces searchchaînes sont-elles nettoyées?
jdscosta91
6
@ jdscosta91 le ?dans le où s'occupera de la désinfection
maison9
7
Les instances @ house9 de %et à l' _intérieur searchne seront pas désinfectées, selon cette approche.
Barry Kelly
8
Lors de l'utilisation de '?' de cette manière, dans les rails, il est converti en une requête paramétrée. Les données dans le paramètre ne sont pas nettoyées (le%), mais il est impossible de changer le contexte de la requête et de la transformer en instructions SQL traitées.
David Hoelzer
50

Au lieu d'utiliser la conditionssyntaxe de Rails 2, utilisez whereplutôt la méthode de 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

REMARQUE: ce qui précède utilise la syntaxe des paramètres au lieu de? placeholder: ces deux doivent générer le même 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

REMARQUE: utilisation ILIKEpour le nom - version postgres insensible à la casse de LIKE

maison9
la source
Est-ce toujours valable pour Rails 5? Parce que si je l'ai, Movie.where("title ILIKE :s", s: search_string)il est traduit SELECT 1 AS one FROM "movies" WHERE (title ILIKE 'test') LIMIT $1par ActiveRecord (Rails 5.1.6) - veuillez noter qu'il n'y a pas de symbole de pourcentage après l'ILIKE)
sekmo
@sekmo - cela devrait fonctionner, le pourcentage irait dans la search_stringvariable. Je pense que la SELECT 1 AS onesortie est dans la console de rails uniquement ou que vous utilisez limit(1)? FYI: Rails 5.1.6 a des problèmes de sécurité, utilisez plutôt 5.1.6.2 ou 5.1.7
maison9
22

Bien que l'interpolation de chaînes fonctionnera, comme votre question spécifie les rails 4, vous pouvez utiliser Arel pour cela et garder votre base de données d'application indépendante.

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
numéros1311407
la source
Devrait vraiment être facile de créer une func pour le faire dynamiquement avec une liste d'attributs
cevaris
Non testé, bien que cela puisse être quelque chose comme ceci: scope search_attributes, -> (query, attributes) {arel_attributes = attributes.map {| a | arel_table [a]} arel_queries = arel_attributes.map {| a | a.matches (query)} return where (arel_queries.reduce {| res, q | res.or q})}
Pedro Rolo
8

ActiveRecord est suffisamment intelligent pour savoir que le paramètre auquel se réfère le ?est une chaîne, et donc il l'entoure de guillemets simples. Vous pouvez, comme le suggère un article, utiliser l'interpolation de chaîne Ruby pour remplir la chaîne avec les %symboles requis . Cependant, cela peut vous exposer à l'injection SQL (ce qui est mauvais). Je vous suggère d'utiliser la CONCAT()fonction SQL pour préparer la chaîne comme ceci:

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

John Cleary
la source
Je viens de l'implémenter dans une application et cela a très bien fonctionné. Merci John.
fuzzygroup
en ce qui concerne les injections, non, cela ne fait aucune différence. La chaîne est insérée dans le sql et les rails devraient l'avoir validée avant (peu importe si a %est ajouté / ajouté ou non). Soit cela fonctionne comme prévu, soit les rails ont un bug majeur affectant les deux cas.
estani le
5

Essayer

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

Voir la documentation sur les conditions AREL pour plus d'informations.

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