I have an after_save callback on a model, and I'm calling previous_changes to see if an attribute (is_complete) changed. Even when the attribute changes, previous_changes returns an empty hash.
Here's the callback:
after_save do |record|
puts "********************"
puts record.previous_changes.to_s
puts record.is_complete
puts "********************"
end
and here's what I get in the log:
********************
{}
true
********************
********************
{}
false
********************
If the value of is_complete changed from true to false, it should be in the previous_changes hash. The update is being done via a normal save! and I'm not reloading the object.
--- UPDATE ---
I hadn't considered this when I posted the question, but my model uses the awesome_nested_set gem, and it appears that this is reloading the object or somehow interfering with the after_save callback. When I comment out acts_as_nested_set, the callback appears to be working fine.
--- UPDATE 2 ---
Fixed this with an around_save callback, which first determines if the attribute changed, then yields, then does the stuff I need it to do after the change has been made in the DB. The working solution looks like this:
around_save do |record, block|
is_complete_changed = true if record.is_complete_changed?
block.call
if is_complete_changed
** do stuff **
end
end
According to ActiveModel::Dirty source code
From line 274
def changes_applied # :doc:
@previously_changed = changes
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
So the changes will be set to @previously_changed
after changes_applied
was called, and changes_apply
was call when save
was called, that means AFTER DOING PERSISTENT WORK
(line 42)
In summary, previous_changes
only has values when the record was actually saved to persistent storage (DB)
So in your callback, you may use record.changed_attributes
, and outside use previously_changed
, it will work fine!
I did not dig super deep, but from the first sight into ActiveModel::Dirty
you can see, that in method previous_changes
:
def previous_changes
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
end
@previously_changed
is not defined anywhere (except for here, which uses the changes
method I speak of below), thus you get the empty (nice and with indifferent access though :D) hash all the time.
What you really want to use, is a changes
method:
def changes
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
It would return your expected
#=> {"is_complete"=>[true, false]}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With