Je veux exécuter une mise à jour brute SQL comme ci-dessous:
update table set f1=? where f2=? and f3=?
Ce SQL sera exécuté par ActiveRecord::Base.connection.execute
, mais je ne sais pas comment passer les valeurs du paramètre dynamique à la méthode.
Est-ce que quelqu'un pourrait me donner de l'aide?
Il ne semble pas que l'API Rails expose les méthodes permettant d'effectuer cette opération de manière générique. Vous pouvez essayer d'accéder à la connexion sous-jacente et d'utiliser ses méthodes, par exemple. pour MySQL:
st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close
Je ne sais pas s'il y a d'autres ramifications à cela (connexions laissées ouvertes, etc.). Je voudrais suivre le code Rails pour une mise à jour normale pour voir ce qu'il fait en dehors de la requête réelle.
L'utilisation de requêtes préparées peut vous faire gagner un peu de temps dans la base de données, mais si vous ne le faites pas un million de fois de suite, vous feriez probablement mieux de créer la mise à jour avec une substitution Ruby normale, par exemple.
ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")
ou en utilisant ActiveRecord comme l'ont dit les commentateurs.
ActiveRecord::Base.connection
a une méthode quote
qui prend une valeur de chaîne (et éventuellement l’objet column). Donc, vous pouvez dire ceci:
ActiveRecord::Base.connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ
Notez que si vous êtes dans un objet de migration Rails ou ActiveRecord), vous pouvez le réduire à:
connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{connection.quote(baz)}
EOQ
UPDATE: Comme l'indique @kolen, vous devez utiliser exec_update
à la place. Cela gérera la citation pour vous et évitera également les fuites de mémoire. La signature fonctionne un peu différemment si:
connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
UPDATE foo
SET bar = $1
EOQ
Ici, le dernier paramètre est un tableau de tuples représentant les paramètres de liaison. Dans chaque tuple, la première entrée est le type de colonne et la seconde est la valeur. Vous pouvez donner nil
pour le type de colonne et Rails fera habituellement ce qu'il faut.
Il y a aussi exec_query
, exec_insert
, et exec_delete
, en fonction de vos besoins.
Vous devriez juste utiliser quelque chose comme:
YourModel.update_all(
ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)
Cela ferait l'affaire. L'utilisation de la méthode ActiveRecord :: Base # send pour appeler sanitize_sql_for_assignment permet à Ruby (au moins la version 1.8.7) d'ignorer la fait que le sanitize_sql_for_assignment est en fait une méthode protégée.
Il serait parfois préférable d'utiliser le nom de la classe parente plutôt que le nom de la table:
# Refers to the current class
self.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
Par exemple, classe de base "Personne", sous-classes (et tables de base de données) "Client" et "Vendeur" à la place de:
Client.where(self.class.primary_key => id).update_all(created _at: timestamp)
Seller.where(self.class.primary_key => id).update_all(created _at: timestamp)
Vous pouvez utiliser un objet de la classe de base de cette manière:
person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
Pourquoi utiliser du SQL brut pour cela?
Si vous avez un modèle pour cela utilisez where
:
f1 = 'foo'
f2 = 'bar'
f3 = 'buzz'
YourModel.where('f1 = ? and f2 = ?', f1, f2).each do |ym|
# or where(f1: f1, f2: f2).each do (...)
ym.update(f3: f3)
end
Si vous n'avez pas de modèle pour cela (uniquement le tableau), vous pouvez créer un fichier et un modèle qui hériteront de ActiveRecord::Base
class YourTable < ActiveRecord::Base
self.table_name = 'your_table' # specify explicitly if needed
end
et encore utiliser where
comme ci-dessus: