Mit ActiveRecord können Sie die alten Werte eines Datensatzes während after_update abrufen

79

Einrichtung anhand eines einfachen Beispiels: Ich habe 1 Tabelle ( Totals), die die Summe der amountSpalten jedes Datensatzes in einer zweiten Tabelle ( Things) enthält.

Wenn a thing.amountaktualisiert wird, möchte ich einfach die Differenz zwischen dem alten und dem neuen Wert hinzufügen total.sum.

Im Moment subtrahiere ich self.amountwährend before_updateund addiere self.amountwährend after_update. Dies setzt viel zu viel Vertrauen in das erfolgreiche Update.

Einschränkung: Ich möchte nicht einfach die Summe aller Transaktionen neu berechnen.

Frage: Ganz einfach, ich möchte während eines after_updateRückrufs auf den ursprünglichen Wert zugreifen . Wie haben Sie sich das ausgedacht?

Update: Ich gehe mit Luke Francls Idee. Während eines after_updateRückrufs haben Sie immer noch Zugriff auf die self.attr_wasWerte, genau das, was ich wollte. Ich habe mich auch für eine after_updateImplementierung entschieden, weil ich diese Art von Logik im Modell behalten möchte. Unabhängig davon, wie ich mich entscheide, Transaktionen in Zukunft zu aktualisieren, weiß ich auf diese Weise, dass ich die Summe der Transaktionen korrekt aktualisiere. Vielen Dank an alle für Ihre Implementierungsvorschläge.

Abel
quelle

Antworten:

147

Das Gleiche gilt für alle Transaktionen.

Das gesagt...

ActiveRecord ab Rails 2.1 verfolgt die Attributwerte eines Objekts. Wenn Sie also ein Attribut haben total, haben Sie eine total_changed?Methode und eine total_wasMethode, die den alten Wert zurückgibt.

Sie müssen Ihrem Modell nichts mehr hinzufügen, um dies zu verfolgen.

Update: Hier ist die Dokumentation für ActiveModel :: Dirty wie gewünscht.

Luke Francl
quelle
Ich dachte, so etwas gibt es. Können Sie auf die entsprechende Dokumentation zu attr_changed verlinken? und attr_was?
Gabe Hollombe
Klar, ich habe der Antwort einen Link hinzugefügt.
Luke Francl
10

Einige andere Leute erwähnen, dass sie all dies in eine Transaktion einwickeln sollen, aber ich denke, das ist für Sie erledigt. Sie müssen nur den Rollback auslösen, indem Sie eine Ausnahme für Fehler in den after_ * -Rückrufen auslösen.

Siehe http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Die gesamte Rückrufkette eines Anrufs zum Speichern, Speichern! Oder Zerstören wird innerhalb einer Transaktion ausgeführt. Das schließt after_ * Hooks ein. Wenn alles gut geht, wird ein COMMIT ausgeführt, sobald die Kette abgeschlossen ist.

Wenn ein before_ * -Rückruf die Aktion abbricht, wird ein ROLLBACK ausgegeben. Sie können auch ein ROLLBACK auslösen, das eine Ausnahme in einem der Rückrufe auslöst, einschließlich after_ * -Hooks. Beachten Sie jedoch, dass in diesem Fall der Client sich dessen bewusst sein muss, da ein gewöhnliches Speichern eine solche Ausnahme auslöst, anstatt stillschweigend false zurückzugeben.

Gabe Hollombe
quelle
10

Wenn Sie "_was" an Ihr Attribut anhängen, erhalten Sie den vorherigen Wert, bevor Sie die Daten speichern.

Diese Methoden werden als Dirty-Methods- Methoden bezeichnet.

Prost!

Manish Shrivastava
quelle
8

So erhalten Sie alle geänderten Felder mit ihren alten bzw. neuen Werten:

person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes        # => {"name" => ["Bill", "Bob"]}
thomax
quelle
5
Ich glaube, die letzte Zeile sollte jetzt person.previous_changesstattperson.changes
Jason Axelson
5

ActiveRecord :: Dirty ist ein in ActiveRecord integriertes Modul zum Verfolgen von Attributänderungen. Sie können also verwenden thing.amount_was, um den alten Wert zu erhalten.

John Topley
quelle
3

Fügen Sie dies Ihrem Modell hinzu:

def amount=(new_value)
    @old_amount = read_attribute(:amount)
    write_attribute(:amount,new_value)
end

Verwenden Sie dann @old_amount in Ihrem after_update-Code.

Daniel Von Fange
quelle
0

Erstens sollten Sie dies in einer Transaktion tun, um sicherzustellen, dass Ihre Daten zusammen geschrieben werden.

Um Ihre Frage zu beantworten, können Sie einfach eine Mitgliedsvariable auf den alten Wert im before_update setzen, auf den Sie dann im after_update zugreifen können. Dies ist jedoch keine sehr elegante Lösung.

jonnii
quelle
Ich kämpfe darum zu sehen, wie ich mithilfe einer Transaktion implementieren würde, was ich will. Befindet sich eine solche Transaktion im Modell oder im Controller? Entferne ich meine Rückrufe 'after_update' und 'before_update'? Wie erhalte ich schließlich den alten Wert, den ich benötige, um den Unterschied zu bestimmen?
Abel
Trotzdem sehe ich, dass ich den Code in den Controller einfügen muss. es ist alles in Ordnung.
Abel
0

Idee 1: Schließen Sie das Update in eine Datenbanktransaktion ein, damit Ihre Totals-Tabelle nicht geändert wird, wenn das Update fehlschlägt: ActiveRecord Transactions-Dokumente

Idee 2: Verstecken Sie den alten Wert in @old_total während des before_update.

Bradheintz
quelle