web-dev-qa-db-fra.com

En utilisant ActiveRecord, y a-t-il un moyen d'obtenir les anciennes valeurs d'un enregistrement pendant after_update

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.

73
Abel

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é.

141
Luke Francl

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é!

10
Manish Shrivastava

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.

9
Gabe Hollombe

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"]}
7
thomax

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.

4
John Topley

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.

3
Daniel Von Fange

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.

0
jonnii

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.

0
bradheintz