web-dev-qa-db-fra.com

Comment spécifier plusieurs valeurs dans où avec l'interface de requête AR dans Rails3

Selon la section 2.2 du guide Rails sur l'interface de requête Active Record ici :

ce qui semble indiquer que je peux passer une chaîne spécifiant la ou les conditions, puis un tableau de valeurs qui devraient être substituées à un moment donné pendant la construction de l'arel. J'ai donc une déclaration qui génère ma chaîne de conditions, qui peut être un nombre variable d'attributs enchaînés avec AND ou OR entre eux, et je passe un tableau comme deuxième argument à la méthode where, et j'obtiens:

ActiveRecord :: PreparedStatementInvalid: nombre incorrect de variables de liaison (1 pour 5)

ce qui m'amène à croire que je fais cela de manière incorrecte. Cependant, je ne trouve rien sur la façon de le faire correctement. Pour reformuler le problème d'une autre manière, je dois passer une chaîne à la méthode where telle que "table.attribute =? AND table.attribute1 =? OR table.attribute1 =?" Avec un nombre inconnu de ces conditions anded ou ored ensemble, puis passez quelque chose, ce que je pensais être un tableau comme deuxième argument qui serait utilisé pour remplacer les valeurs de la première chaîne de conditions d'argument. Est-ce la bonne approche, ou, je manque-t-il quelque autre énorme concept quelque part et je me trompe? Je pense que d'une manière ou d'une autre, cela doit être possible, à moins de générer simplement une chaîne SQL brute.

29
wkhatch

On dirait que vous faites quelque chose comme ça:

Model.where("attribute = ? OR attribute2 = ?", [value, value])

Alors que vous devez le faire:

# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)

Jetez un œil à http://guides.rubyonrails.org/active_record_querying.html#array-conditions pour plus de détails sur la façon dont cela fonctionne.

24
Dan Cheail

C'est en fait assez simple:

Model.where(attribute: [value1,value2])
36
nfriend21

Au lieu de passer plusieurs fois le même paramètre à where() comme ceci

User.where(
  "first_name like ? or last_name like ? or city like ?", 
  "%#{search}%", "%#{search}%", "%#{search}%"
)

vous pouvez facilement fournir un hachage

User.where(
  "first_name like :search or last_name like :search or city like :search",
  {search: "%#{search}%"}
)

cela rend votre requête beaucoup plus lisible pour les longues listes d'arguments.

21
ToniTornado

On dirait que vous faites quelque chose comme ça:

Model.where("attribute = ? OR attribute2 = ?", [value, value])

Alors que vous devez le faire:

#notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value) Have a

regardez http://guides.rubyonrails.org/active_record_querying.html#array-conditions pour plus de détails sur la façon dont cela fonctionne.

Était vraiment proche. Vous pouvez transformer un tableau en une liste d'arguments avec * ma_liste.

Model.where("id = ? OR id = ?", *["1", "2"])

OR

params = ["1", "2"]
Model.where("id = ? OR id = ?", *params)

Devrait marcher

12
Max Wolff

Si vous voulez enchaîner une liste ouverte de conditions (noms et valeurs d'attributs), je suggérerais d'utiliser une table arel.

Il est un peu difficile de donner des détails car votre question est tellement vague, je vais donc vous expliquer comment procéder pour un cas simple d'un modèle Post et de quelques attributs, disons title, summary et user_id (c'est-à-dire un utilisateur has_many publie).

Tout d'abord, obtenez la table arel pour le modèle:

table = Post.arel_table

Ensuite, commencez à construire votre prédicat (que vous utiliserez éventuellement pour créer une requête SQL):

relation = table[:title].eq("Foo")
relation = relation.or(table[:summary].eq("A post about foo"))
relation = relation.and(table[:user_id].eq(5))

Ici, table[:title], table[:summary] Et table[:user_id] Sont des représentations de colonnes dans la table posts. Lorsque vous appelez table[:title].eq("Foo"), vous créez un prédicat, à peu près équivalent à une condition de recherche (obtenez toutes les lignes dont la colonne de titre est égale à "Foo"). Ces prédicats peuvent être chaînés avec and et or.

Lorsque votre prédicat d'agrégation est prêt, vous pouvez obtenir le résultat avec:

Post.where(relation)

qui va générer le SQL:

SELECT "posts".* FROM "posts"
WHERE (("posts"."title" = "Foo" OR "posts"."summary" = "A post about foo")
AND "posts"."user_id" = 5)

Vous obtiendrez ainsi tous les articles qui ont soit le titre "Foo" soit le résumé "A post about foo", et qui appartiennent à un utilisateur avec id 5.

Remarquez comment les prédicats arel peuvent être enchaînés à l'infini pour créer des requêtes de plus en plus complexes. Cela signifie que si vous avez (disons) un hachage de paires attribut/valeur, et un moyen de savoir s'il faut utiliser AND ou OR sur chacun d'eux, vous pouvez les parcourir un par un un et construire votre condition:

relation = table[:title].eq("Foo")
hash.each do |attr, value|
  relation = relation.and(table[attr].eq(value))
  # or relation = relation.or(table[attr].eq(value)) for an OR predicate
end
Post.where(relation)

Outre la facilité de chaînage des conditions, un autre avantage des tables arel est qu'elles sont indépendantes de la base de données, vous n'avez donc pas à vous soucier de savoir si votre requête MySQL fonctionnera dans PostgreSQL, etc.

Voici un Railscast avec plus sur arel: http://railscasts.com/episodes/215-advanced-queries-in-Rails-3?view=asciicast

J'espère que cela pourra aider.

4
Chris Salzberg

Vous pouvez utiliser un hachage plutôt qu'une chaîne. Construisez un hachage avec autant de conditions et de valeurs correspondantes que vous allez avoir et mettez-le dans le premier argument de la méthode where.

2
Chris Root

FAUX C'est ce que je faisais pour une raison quelconque.

keys = params[:search].split(',').map!(&:downcase)
# keys are now ['brooklyn', 'queens']

query = 'lower(city) LIKE ?'

if keys.size > 1
  # I need something like this depending on number of keys 
  # 'lower(city) LIKE ? OR lower(city) LIKE ? OR lower(city) LIKE ?'
 query_array = []
 keys.size.times { query_array << query }
 #['lower(city) LIKE ?','lower(city) LIKE ?']
 query = query_array.join(' OR ')
 # which gives me 'lower(city) LIKE ? OR lower(city) LIKE ?'
end
# now I can query my model
# if keys size is one then keys are just 'brooklyn', 
# in this case it is  'brooklyn', 'queens'
# @posts = Post.where('lower(city) LIKE ? OR lower(city) LIKE ?','brooklyn', 'queens' )
@posts = Post.where(query, *keys )

maintenant cependant - oui - c'est très simple. comme l'a mentionné nfriend21

Model.where(attribute: [value1,value2])

fait la même chose

1
AndreiMotinga