Avec ActiveRecord, existe-t-il un moyen de récupérer les anciennes valeurs d’un enregistrement pendant after_update?
-
03-07-2019 - |
Question
Configuration à l'aide d'un exemple simple: un tableau ( Totaux
) contient la somme de la colonne montant
de chaque enregistrement. dans un deuxième tableau ( Things
).
Quand un thing.amount
est mis à jour, j'aimerais simplement ajouter la différence entre l'ancienne valeur et la nouvelle valeur à total.sum
.
Pour le moment, je soustrais self.amount
pendant before_update
et j'ajoute self.amount
pendant after_update
. Cela place beaucoup trop de confiance dans la réussite de la mise à jour.
Contrainte: je ne souhaite pas simplement recalculer la somme de toutes les transactions.
Question: Tout simplement, j'aimerais accéder à la valeur d'origine lors d'un rappel after_update
. Comment avez-vous trouvé cela?
Mise à jour: J'y vais avec l'idée de Luke Francl. Pendant un rappel after_update
, vous avez toujours accès aux valeurs self.attr_was
, ce qui est exactement ce que je voulais. J'ai également décidé d'utiliser une implémentation after_update
car je souhaite conserver ce type de logique dans le modèle. Ainsi, peu importe la façon dont je déciderai de mettre à jour les transactions à l'avenir, je saurai que je mets à jour correctement la somme des transactions. Merci à tous pour vos suggestions de mise en œuvre.
La solution
Idem ce que tout le monde dit à propos des transactions.
Cela dit ...
ActiveRecord à partir de Rails 2.1 assure le suivi des valeurs d'attribut d'un objet. Ainsi, si vous avez un attribut total
, vous aurez une méthode total_changed?
et une méthode total_was
qui renvoie l'ancienne valeur.
Il n'est pas nécessaire d'ajouter quoi que ce soit à votre modèle pour en suivre l'évolution.
Mise à jour: Voici la documentation pour ActiveModel :: Dirty à la demande.
Autres conseils
D'autres personnes mentionnent que tout cela était emballé dans une transaction, mais je pense que c'est fait pour vous; il vous suffit de déclencher la restauration en générant une exception pour les erreurs dans les rappels after_ *.
Voir http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
L'ensemble de la chaîne de rappel d'un appel de sauvegarde, de sauvegarde !, ou de destruction s'exécute dans une transaction. Cela inclut after_ * hooks. Si tout se passe bien, COMMIT est exécuté une fois la chaîne terminée.
Si un rappel before_ * annule l'action, un ROLLBACK est émis. Vous pouvez également déclencher un ROLLBACK en soulevant une exception dans n'importe lequel des callbacks, y compris after_ * hooks. Notez cependant que, dans ce cas, le client doit en être conscient, car une sauvegarde ordinaire lève cette exception au lieu de retourner silencieusement la valeur false.
Ajout de " _was " à votre attribut vous donnera la valeur précédente avant de sauvegarder les données.
Ces méthodes sont appelées méthodes incorrectes .
Salut!
Pour obtenir tous les champs modifiés, avec leurs anciennes et leurs nouvelles valeurs, respectivement:
person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes # => {"name" => ["Bill", "Bob"]}
ActiveRecord :: Dirty est un module intégré à ActiveRecord pour le suivi. attribuer des changements. Vous pouvez donc utiliser thing.amount_was
pour obtenir l'ancienne valeur.
Ajoutez ceci à votre modèle:
def amount=(new_value)
@old_amount = read_attribute(:amount)
write_attribute(:amount,new_value)
end
Puis utilisez @old_amount dans votre code after_update.
Tout d'abord, vous devriez le faire dans une transaction pour vous assurer que vos données sont écrites ensemble.
Pour répondre à votre question, vous pouvez simplement définir une variable membre sur l'ancienne valeur de before_update, à laquelle vous pourrez ensuite accéder dans after_update. Toutefois, cette solution n'est pas très élégante.
Idée 1: encapsulez la mise à jour dans une transaction de base de données, de sorte qu'en cas d'échec de la mise à jour, votre table de totaux ne soit pas modifiée: Documents ActiveRecord Transactions
Idée 2: Cachez l'ancienne valeur dans @old_total pendant before_update.