Using ActiveRecord, is there a way to get the old

2019-03-08 07:04发布

Setup using a simple example: I've got 1 table (Totals) that holds the sum of the amount column of each record in a second table (Things).

When a thing.amount gets updated, I'd like to simply add the difference between the old value and the new value to total.sum.

Right now I'm subtracting self.amount during before_update and adding self.amount during after_update. This places WAY too much trust in the update succeeding.

Constraint: I don't want to simply recalculate the sum of all the transactions.

Question: Quite simply, I'd like to access the original value during an after_update callback. What ways have you come up with do this?

Update: I'm going with Luke Francl's idea. During an after_update callback you still have access to the self.attr_was values which is exactly what I wanted. I also decided to go with an after_update implementation because I want to keep this kind of logic in the model. This way, no matter how I decide to update transactions in the future, I'll know that I'm updating the sum of the transactions correctly. Thanks to everyone for your implementation suggestions.

8条回答
小情绪 Triste *
2楼-- · 2019-03-08 07:12

ActiveRecord::Dirty is a module that's built into ActiveRecord for tracking attribute changes. So you can use thing.amount_was to get the old value.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-03-08 07:12

Idea 1: Wrap the update in a database transaction, so that if the update fails your Totals table isn't changed: ActiveRecord Transactions docs

Idea 2: Stash the old value in @old_total during the before_update.

查看更多
闹够了就滚
4楼-- · 2019-03-08 07:20

To get all changed fields, with their old and new values respectively:

person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes        # => {"name" => ["Bill", "Bob"]}
查看更多
欢心
5楼-- · 2019-03-08 07:23

Some other folks are mentioning wrapping all this in a transaction, but I think that's done for you; you just need to trigger the rollback by raising an exception for errors in the after_* callbacks.

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

The entire callback chain of a save, save!, or destroy call runs within a transaction. That includes after_* hooks. If everything goes fine a COMMIT is executed once the chain has been completed.

If a before_* callback cancels the action a ROLLBACK is issued. You can also trigger a ROLLBACK raising an exception in any of the callbacks, including after_* hooks. Note, however, that in that case the client needs to be aware of it because an ordinary save will raise such exception instead of quietly returning false.

查看更多
Bombasti
6楼-- · 2019-03-08 07:23

Add this to your model:

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

Then use @old_amount in your after_update code.

查看更多
beautiful°
7楼-- · 2019-03-08 07:23

Firstly, you should be doing this in a transaction to ensure that your data gets written together.

To answer your question, you could just set a member variable to the old value in the before_update, which you can then access in the after_update, however this isn't a very elegant solution.

查看更多
登录 后发表回答