ActiveRecordを使用して、after_update中にレコードの古い値を取得する方法はありますか
-
03-07-2019 - |
質問
簡単な例を使用したセットアップ:各レコードの amount
列の合計を保持する1つのテーブル( Totals
)があります2番目のテーブル( Things
)。
thing.amount
が更新されたら、古い値と新しい値の差を total.sum
に単純に追加したいと思います。
今、 before_update
で self.amount
を差し引き、 after_update
で self.amount
を追加しています。これにより、更新の成功に信頼が寄せられます。
制約:すべてのトランザクションの合計を単純に再計算したくありません。
質問:簡単に言えば、 after_update
コールバック中に元の値にアクセスしたいのです。これをどのように思いつきましたか?
更新: Luke Franclのアイデアを採用します。 after_update
コールバックの間、 self.attr_was
の値にアクセスできますが、これはまさに私が望んでいたものです。また、この種のロジックをモデルに保持したいので、 after_update
の実装を使用することにしました。これにより、将来どのようにトランザクションを更新することにしたとしても、トランザクションの合計を正しく更新していることがわかります。実装の提案に感謝します。
解決
トランザクションについて誰もが言っていることと同じ。
それは...
Rails 2.1以降のActiveRecordは、オブジェクトの属性値を追跡します。したがって、属性 total
がある場合、 total_changed?
メソッドと、古い値を返す total_was
メソッドがあります。
これを追跡するためにモデルに何かを追加する必要はもうありません。
更新:以下は、 ActiveModel :: Dirtyのドキュメントです。 要求どおり。
他のヒント
これをすべてトランザクションでラッピングすることに言及している人もいますが、それはあなたのためにできたと思います。 after_ *コールバックでエラーの例外を発生させることにより、ロールバックをトリガーする必要があります。
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.htmlを参照してください。
save、save !、またはdestroy呼び出しのコールバックチェーン全体がトランザクション内で実行されます。これにはafter_ *フックが含まれます。すべてがうまくいけば、チェーンが完了するとCOMMITが実行されます。
before_ *コールバックがアクションをキャンセルすると、ROLLBACKが発行されます。また、after_ *フックを含む任意のコールバックで例外を発生させるROLLBACKをトリガーできます。ただし、その場合、通常の保存ではfalseを静かに返すのではなく、このような例外が発生するため、クライアントはそれを認識する必要があります。
変更されたすべてのフィールドを、それぞれ古い値と新しい値で取得するには:
person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes # => {"name" => ["Bill", "Bob"]}
ActiveRecord :: Dirty は、追跡のためにActiveRecordに組み込まれているモジュールです属性の変更。したがって、 thing.amount_was
を使用して古い値を取得できます。
これをモデルに追加します:
def amount=(new_value)
@old_amount = read_attribute(:amount)
write_attribute(:amount,new_value)
end
その後、after_updateコードで@old_amountを使用します。
まず、データを一緒に書き込むために、トランザクションでこれを行う必要があります。
質問に答えるには、before_updateでメンバー変数を古い値に設定するだけで、after_updateでアクセスできますが、これは非常にエレガントなソリューションではありません。
アイデア1:更新がデータベーストランザクションでラップされるため、更新が失敗してもTotalsテーブルは変更されません。 ActiveRecord Transactionsドキュメント
アイデア2:before_update中に@old_totalの古い値を隠します。