ActiveRecordを使用して、after_update中にレコードの古い値を取得する方法はありますか


81

簡単な例を使用したセットアップは:私は1つのテーブル(持っているTotalsの合計保持)amount第二の表の各レコードの列を(Things)。

thing.amountが更新されたら、古い値と新しい値の差をに追加したいと思いますtotal.sum

今、私はself.amount中に減算しbefore_update、中に加算self.amountしていafter_updateます。これにより、更新の成功に対する信頼が非常に高くなります。

制約: すべてのトランザクションの合計を単純に再計算したくありません。

質問:簡単に言うと、after_updateコールバック中に元の値にアクセスしたいと思います。これを行うにはどのような方法がありますか?

更新: LukeFranclのアイデアを採用します。after_updateコールバック中も、self.attr_wasまさに私が望んでいた値にアクセスできます。またafter_update、この種のロジックをモデルに保持したいので、実装を採用することにしました。このようにして、将来どのようにトランザクションを更新することにしたとしても、トランザクションの合計を正しく更新していることがわかります。実装の提案をしてくれた皆さんに感謝します。

回答:


149

誰もが取引について言っていることと同じです。

そうは言っても...

Rails 2.1以降のActiveRecordは、オブジェクトの属性値を追跡します。したがって、属性totalがある場合は、total_changed?メソッドとtotal_was古い値を返すメソッドがあります。

これを追跡するためにモデルに何かを追加する必要はありません。

更新:要求に応じて、ActiveModel :: Dirtyのドキュメントを次に示します。


こんなものがあると思いました。attr_changedに関する関連ドキュメントにリンクできますか?とattr_was?
Gabe Hollombe

確かに、私は答えへのリンクを追加しました。
ルークフランクル

10

他の何人かの人々は、これらすべてをトランザクションでラップすることに言及していますが、それはあなたのために行われたと思います。after_ *コールバックのエラーの例外を発生させて、ロールバックをトリガーする必要があります。

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.htmlを参照してください

save、save !、またはdestroy呼び出しのコールバックチェーン全体がトランザクション内で実行されます。これにはafter_ *フックが含まれます。すべてがうまくいけば、チェーンが完了するとCOMMITが実行されます。

before_ *コールバックがアクションをキャンセルすると、ROLLBACKが発行されます。after_ *フックを含む、任意のコールバックで例外を発生させるROLLBACKをトリガーすることもできます。ただし、その場合、通常の保存では静かにfalseを返すのではなく、そのような例外が発生するため、クライアントはそれを認識する必要があることに注意してください。



8

変更されたすべてのフィールドを、それぞれ古い値と新しい値とともに取得するには、次のようにします。

person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes        # => {"name" => ["Bill", "Bob"]}

5
最後の行は、person.previous_changes代わりに読む必要があると思いますperson.changes
Jason Axelson 2017


3

これをモデルに追加します。

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

次に、after_updateコードで@old_amountを使用します。


0

まず、データが一緒に書き込まれるように、トランザクションでこれを行う必要があります。

質問に答えるには、メンバー変数をbefore_updateの古い値に設定するだけで、after_updateでアクセスできますが、これはあまり洗練されたソリューションではありません。


トランザクションを使用して必要なものを実装する方法を見つけるのに苦労しています。このようなトランザクションはモデルまたはコントローラーに存在しますか?'after_update'および 'before_update'コールバックを削除しますか?最後に、違いを判断するために必要な古い値を取得するにはどうすればよいですか?
アベル

気にしないでください、私はコードをコントローラーに配置する必要があることがわかります。よかったです。
アベル

0

アイデア1:データベーストランザクションで更新をラップし、更新が失敗してもTotalsテーブルが変更されないようにします:ActiveRecord Transactions docs

アイデア2:before_update中に@old_totalに古い値を隠します。


0

更新後に特定のフィールドの値を取得する場合は、field_before_last_saveメソッドを使用できます。

Example:

u = User.last
u.update(name: "abc")

u.name_before_last_save will give you old value (before update value)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.