immosquare Active Record Change Tracker
This extension allows you to automatically track changes to your ActiveRecord models. It records changes to specified attributes whenever a record is saved.
Installation
gem "immosquare-active-record-change-tracker"Generate the migration:
rails generate immosquare_active_record_change_tracker:install
# create_table(:active_record_change_trackers) do |t|
# t.references(:recordable, :polymorphic => true, :foreign_key => false, :index => false, :null => false)
# t.references(:modifier, :polymorphic => true, :foreign_key => false, :index => false, :null => true)
# t.string(:event, :null => true, :limit => 10)
# t.text(:data, :null => true, :limit => 4_294_967_295)
# t.datetime(:created_at, :null => false)
# end
# add_index(:active_record_change_trackers, [:recordable_type, :recordable_id])
# add_index(:active_record_change_trackers, [:modifier_type, :modifier_id])Then run the migration :
rails db:migrateUsage
To enable history tracking for a model, add track_active_record_changes to your model
class YourModel < ApplicationRecord
track_active_record_changes
# rest of your model code...
endBy default, changes to all attributes (except created_at and updated_at) will be tracked.
You can specify options to include or exclude specific attributes:
Exclude certain attributes :
class YourModel < ApplicationRecord
track_active_record_changes(except: [:attribute1, :attribute2])
# rest of your model code...
endThis will track changes to all attributes except :attribute1 and :attribute2.
Include only certain attributes :
class YourModel < ApplicationRecord
track_active_record_changes(only: [:attribute3, :attribute4])
# rest of your model code...
endThis will track changes only to :attribute3 and :attribute4.
Specify a modifier using a block :
The modifier can be specified by providing a block to track_active_record_changes, which allows you to capture dynamic context at the time changes are saved. (https://zainab-alshaikhli01.medium.com/activesupport-currentattributes-e3d43270207c)
class YourModel < ApplicationRecord
track_active_record_changes(except: [:attribute1]) do
## Your Logic to get the modifier
Current.admin.present? ? Current.admin : Current.user
end
# rest of your model code...
endSecurity Considerations
The gem stores the before/after values of every changed attribute in the data JSON column of active_record_change_trackers. Two things to keep in mind:
1. Filter sensitive attributes
By default, every attribute except created_at and updated_at is tracked. If you enable tracking on a model that holds sensitive data, those values will be persisted in plaintext (or as their stored representation) inside the history table.
Always exclude credentials, tokens and regulated PII explicitly:
class User < ApplicationRecord
track_active_record_changes(except: [
:password_digest,
:encrypted_password,
:reset_password_token,
:confirmation_token,
:unlock_token,
:remember_token,
:api_token
])
endThe safer pattern is to use only: with an explicit allow-list of the attributes you actually want to audit, rather than relying on except: to remember every sensitive field.
2. Protect access to history_records
The gem does not enforce any authorization on the history_records association. Never expose it through an API, a serializer or an admin view without an access control layer — doing so leaks the full diff history of the record, including any field you forgot to exclude in step 1.
Considerations for Deletion
The gem is compatible with the paranoia gem (https://github.com/rubysherpas/paranoia) :
- If your model has
acts_as_paranoid, then the deletion of a record will be recorded in theactive_record_change_trackerstable with the eventdestroy, and the records ofcreateandupdatewill be retained. - A really_destroy! command will completely delete the record from the
active_record_change_trackerstable. - Without this gem, the deletion of a record will not be recorded in the
active_record_change_trackerstable, and the records ofcreateandupdatewill be deleted.
Order matters with paranoia
acts_as_paranoid must be declared before track_active_record_changes. The tracker reads paranoid? at macro-call time to decide between dependent: :destroy (hard cleanup) and after_real_destroy (paranoia-aware cleanup). If you call track_active_record_changes first, the tracker will treat the model as non-paranoid and hard-delete the history on every soft-delete.
class YourModel < ApplicationRecord
acts_as_paranoid # first
track_active_record_changes # then
endAccessing Change History
Each model that includes track_active_record_changes automatically has access to its change history through the history_records association. The history records are ordered by created_at in descending order, meaning the most recent changes are listed first.
Example:
class YourModel < ApplicationRecord
track_active_record_changes
# rest of your model code...
endAccess change history :
your_model_instance.history_recordsContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/immosquare/immosquare-active-record-change-tacker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open-source under the terms of the MIT License.