Recently needing to ensure that financial transactions remain unchanged, I went looking about to see if anyone else had solved this. I had done it previously, and my method turns out to be useful.
Sometimes, you want to ensure that once created, an ActiveRecord model object record does not get changed. In my case, these are records of transactions against payment methods. They serve as an audit trail and history of financial transactions performed by users in our system (yay Brewtoad!!).
In this case, I wanted the entire record to remain unchangeable. There are two approaches to this, either hooking before_save or using a validation. I prefer using a validation as it also provides some feedback to the caller.
force_immutable will only invalidate the operation when the record
is “dirty” which is checked with the
.changed? method, and the
record has already been saved to the database which is checked by the
As per usual, the error is added to the
errors collection and the
validation will fail.
But the extra thing I’m adding here is I’m reloading the record to preserve for the caller the immutable state of the record.
NOTE: There is a good argument to be made for NOT doing this as well, and having the caller deal with the issue.
In some cases, I only want some attribute fields in the record to be
immutable, such as an order’s PO Number. There is a Rails method
ActiveRecord::ReadOnly::ClassMethods however it
simple acts silently and I’d like to provide some feedback to the
I use the same mechanism of the validation, but here individual attributes are checked to see if they’ve changed in some way.
force_immutable method, the validation is only performed
on an already persisted record. For every element of the
array, it is checked against the list of changed attributes. If an
IMMUTABLE element has been changed, an error for that attribute is
It is possible also to restore the previous value, similarly to the
way the entire record is reloaded. The
module includes the previous values allowing you to restore them if
you wish. It’s a bit more work than simply reloading the entire record
as I did above, because the caller may have made legitimate changes as
well as the changes to immutable attributes.