簡単な例を使用した設定: 1つのテーブル(Totals
)があり、2番目のテーブルの各レコードのamount
列の合計を保持しています(Things
)。
thing.amount
が更新されたら、古い値と新しい値の差をtotal.sum
に単純に追加したいと思います。
現在、self.amount
中にbefore_update
を減算し、self.amount
中にafter_update
を追加しています。これにより、更新の成功に信頼が寄せられます。
制約:すべてのトランザクションの合計を単純に再計算したくありません。
質問:簡単に言うと、after_update
コールバック中に元の値にアクセスしたいと思います。これをどのように思いつきましたか?
更新:私はルーク・フランクルの考えに沿っています。 after_update
コールバック中、self.attr_was
値にアクセスできますが、これはまさに私が望んでいたものです。また、この種のロジックをモデルに保持したいので、after_update
実装を使用することにしました。このように、将来どのようにトランザクションを更新することにしたとしても、トランザクションの合計を正しく更新していることがわかります。実装の提案に感謝します。
誰もがトランザクションについて言っていることと同じです。
とはいえ...
Rails 2.1の時点でのActiveRecordは、オブジェクトの属性値を追跡します。したがって、属性total
があれば、total_changed?
メソッドとtotal_was
古い値を返すメソッド。
これを追跡するためにモデルに何かを追加する必要はありません。
Update:以下は、要求に応じて 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 docs
アイデア2:before_update中に@old_totalの古い値を隠します。