Configuration à l'aide d'un exemple simple: J'ai 1 table (Totals
) qui contient la somme de la colonne amount
de chaque enregistrement d'une deuxième table (Things
).
Lorsqu'un thing.amount
est mis à jour, je voudrais simplement ajouter la différence entre l'ancienne valeur et la nouvelle valeur à total.sum
.
En ce moment je soustrais self.amount
pendant before_update
et en ajoutant self.amount
pendant after_update
. Cela place beaucoup trop de confiance dans la réussite de la mise à jour.
Contrainte: Je ne veux pas simplement recalculer la somme de toutes les transactions.
Question: Tout simplement, je voudrais accéder à la valeur d'origine pendant un after_update
rappeler. De quelles façons avez-vous trouvé cela?
pdate: Je vais avec l'idée de Luke Francl. Au cours d'un after_update
rappel vous avez toujours accès au self.attr_was
valeurs qui est exactement ce que je voulais. J'ai aussi décidé d'aller avec un after_update
implémentation car je souhaite conserver ce type de logique dans le modèle. De cette façon, peu importe comment je décide 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.
Idem ce que tout le monde dit sur les transactions.
Cela dit...
ActiveRecord à partir de Rails 2.1 garde une trace des valeurs d'attribut d'un objet. Donc, si vous avez un attribut total
, vous aurez un total_changed?
méthode et un total_was
méthode qui renvoie l'ancienne valeur.
Il n'est plus nécessaire d'ajouter quoi que ce soit à votre modèle pour en garder la trace.
Mise à jour: Voici la documentation pour ActiveModel :: Dirty comme demandé.
Ajouter "_was" à votre attribut vous donnera la valeur précédente avant d'enregistrer les données.
Ces méthodes sont appelées méthodes méthodes sales .
À votre santé!
D'autres personnes mentionnent envelopper tout cela dans une transaction, mais je pense que c'est fait pour vous; il vous suffit de déclencher la restauration en levant 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'une sauvegarde, d'une sauvegarde! Ou d'une destruction s'exécute dans une transaction. Cela inclut les crochets after_ *. Si tout se passe bien, un 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 levant une exception dans l'un des rappels, y compris les hooks after_ *. Notez, cependant, que dans ce cas, le client doit en être conscient car une sauvegarde ordinaire lèvera une telle exception au lieu de retourner tranquillement false.
Pour obtenir tous les champs modifiés, avec leurs anciennes et 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 suivre les changements d'attributs. 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
Utilisez ensuite @old_amount dans votre code after_update.
Tout d'abord, vous devez 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 dans before_update, à laquelle vous pouvez ensuite accéder dans after_update, mais ce n'est pas une solution très élégante.
Idée 1: encapsulez la mise à jour dans une transaction de base de données, de sorte que si la mise à jour échoue, votre table Totaux ne soit pas modifiée: ActiveRecord Transactions docs
Idée 2: cachez l'ancienne valeur dans @old_total pendant la before_update.