Class: RailsAuditLog::AuditLogEntry
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- RailsAuditLog::AuditLogEntry
- Defined in:
- app/models/rails_audit_log/audit_log_entry.rb
Overview
Represents a single audited event (create, update, or destroy) for one ActiveRecord record.
Columns
- event
-
One of
"create","update","destroy". - item_type
-
Class name of the audited record (e.g.
"Article"). - item_id
-
Primary key of the audited record.
- object_changes
-
JSON hash of attribute changes in
[from, to]form. All three event types use the same format:createstores[nil, new]for every column,updatestores[old, new]for changed columns only,destroystores[final, nil]for every column. - object
-
JSON snapshot of the record’s full attributes before the change (stored when #store_snapshot is
true). - whodunnit_snapshot
-
Display name of the actor at the time of the change.
- actor_type / actor_id
-
Polymorphic reference to the actor record.
- reason
-
Optional free-text reason string.
- metadata
-
Arbitrary JSON hash (request IP, custom lambdas, etc.).
Constant Summary collapse
- EVENTS =
%w[create update destroy].freeze
- BLOB_COLUMNS =
%w[object_changes object metadata].freeze
- PERIODS =
{ "1h" => 1.hour, "24h" => 24.hours, "7d" => 7.days }.freeze
- ENCRYPTION_MARKER =
"__ral_enc__"
Event scopes collapse
-
#created_events ⇒ ActiveRecord::Relation
Entries for
createevents. -
#destroyed_events ⇒ ActiveRecord::Relation
Entries for
destroyevents. -
#updated_events ⇒ ActiveRecord::Relation
Entries for
updateevents.
Actor / resource scopes collapse
-
#by_actor ⇒ ActiveRecord::Relation
Entries written by a specific actor.
-
#for_resource ⇒ ActiveRecord::Relation
Entries for a specific resource class or instance.
Time scopes collapse
-
#for_period ⇒ ActiveRecord::Relation
Entries within a named period.
-
#since ⇒ ActiveRecord::Relation
Entries created at or after
time. -
#until ⇒ ActiveRecord::Relation
Entries created at or before
time.
Class Method Summary collapse
- .configure_connection! ⇒ Object private
Instance Method Summary collapse
-
#changed_attributes ⇒ Array<String>
Returns the list of attribute (and association) names that changed in this entry, derived from the keys of
object_changes. -
#diff ⇒ Hash{String => Hash}
Returns
object_changesin a named-key format convenient for display. -
#next ⇒ AuditLogEntry?
Returns the entry immediately after this one in the version chain for the same record (higher
id), ornilif this is the last entry. -
#object ⇒ Hash?
Returns the decrypted
objectsnapshot hash. -
#object_changes ⇒ Hash?
Returns the decrypted
object_changeshash. -
#previous ⇒ AuditLogEntry?
Returns the entry immediately before this one in the version chain for the same record (lower
id), ornilif this is the first entry. -
#reify ⇒ ActiveRecord::Base?
Reconstructs and returns the record’s state before this entry’s change.
-
#slim ⇒ ActiveRecord::Relation
Omits the three JSON blob columns (
object_changes,object,metadata) from theSELECT. -
#touching ⇒ ActiveRecord::Relation
Entries where
object_changescontains a key matchingattribute.
Class Method Details
.configure_connection! ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
30 31 32 33 34 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 30 def self.configure_connection! return unless (opts = RailsAuditLog.connects_to) connects_to(**opts) end |
Instance Method Details
#by_actor ⇒ ActiveRecord::Relation
Entries written by a specific actor.
75 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 75 scope :by_actor, ->(actor) { where(actor_type: actor.class.name, actor_id: actor.id) } |
#changed_attributes ⇒ Array<String>
Returns the list of attribute (and association) names that changed in this entry, derived from the keys of object_changes.
215 216 217 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 215 def changed_attributes object_changes&.keys || [] end |
#created_events ⇒ ActiveRecord::Relation
Entries for create events.
48 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 48 scope :created_events, -> { where(event: "create") } |
#destroyed_events ⇒ ActiveRecord::Relation
Entries for destroy events.
56 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 56 scope :destroyed_events, -> { where(event: "destroy") } |
#diff ⇒ Hash{String => Hash}
Returns object_changes in a named-key format convenient for display.
226 227 228 229 230 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 226 def diff return {} unless object_changes object_changes.transform_values { |from_to| { from: from_to[0], to: from_to[1] } } end |
#for_period ⇒ ActiveRecord::Relation
Entries within a named period. Valid keys: "1h", "24h", "7d".
114 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 114 scope :for_period, ->(period) { where(created_at: PERIODS[period].ago..) } |
#for_resource ⇒ ActiveRecord::Relation
Entries for a specific resource class or instance. Pass a class to get all entries for that type; pass an instance for one record.
86 87 88 89 90 91 92 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 86 scope :for_resource, lambda { |resource| if resource.is_a?(Class) where(item_type: resource.name) else where(item_type: resource.class.name, item_id: resource.id) end } |
#next ⇒ AuditLogEntry?
Returns the entry immediately after this one in the version chain for the same record (higher id), or nil if this is the last entry.
192 193 194 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 192 def next self.class.where(item_type: item_type, item_id: item_id).where("id > ?", id).order(id: :asc).first end |
#object ⇒ Hash?
Returns the decrypted object snapshot hash. Transparent to callers.
207 208 209 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 207 def object decrypt_if_encrypted(super) end |
#object_changes ⇒ Hash?
Returns the decrypted object_changes hash. Transparent to callers —encrypted and non-encrypted entries behave identically.
200 201 202 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 200 def object_changes decrypt_if_encrypted(super) end |
#previous ⇒ AuditLogEntry?
Returns the entry immediately before this one in the version chain for the same record (lower id), or nil if this is the first entry.
184 185 186 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 184 def previous self.class.where(item_type: item_type, item_id: item_id).where("id < ?", id).order(id: :desc).first end |
#reify ⇒ ActiveRecord::Base?
Reconstructs and returns the record’s state before this entry’s change. Uses the object snapshot when available; falls back to deriving prior state from object_changes (or the live record for update entries).
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 149 def reify return nil if event == "create" klass = item_type.constantize if object.present? instance = klass.new instance.assign_attributes(object.except("id")) instance.id = object.fetch("id") { item_id } return instance end # Fallback: diff-only mode or entries recorded before snapshot support. # Filter to column names so association-change entries (e.g. tags, comments) # don't get assigned to the record as if they were scalar attributes. column_names = klass.column_names.map(&:to_s) from_attrs = (object_changes || {}) .select { |k, _| column_names.include?(k) } .transform_values { |from_to| from_to[0] } if event == "update" record = klass.find_by(id: item_id) from_attrs = record.attributes.merge(from_attrs) if record end instance = klass.new instance.assign_attributes(from_attrs.except("id")) instance.id = from_attrs.fetch("id") { item_id } instance end |
#since ⇒ ActiveRecord::Relation
Entries created at or after time.
102 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 102 scope :since, ->(time) { where(created_at: time..) } |
#slim ⇒ ActiveRecord::Relation
Omits the three JSON blob columns (object_changes, object, metadata) from the SELECT. Use on index/listing queries where blobs are not displayed to reduce I/O and avoid deserializing large payloads.
123 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 123 scope :slim, -> { select(column_names - BLOB_COLUMNS) } |
#touching ⇒ ActiveRecord::Relation
Entries where object_changes contains a key matching attribute. Uses json_extract on SQLite/MySQL and ->> on PostgreSQL.
133 134 135 136 137 138 139 140 141 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 133 scope :touching, ->(attribute) { if connection.adapter_name =~ /PostgreSQL/i # :nocov: where("object_changes->>? IS NOT NULL", attribute.to_s) # :nocov: else where("json_extract(object_changes, ?) IS NOT NULL", "$.#{attribute}") end } |
#until ⇒ ActiveRecord::Relation
Entries created at or before time.
108 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 108 scope :until, ->(time) { where(created_at: ..time) } |
#updated_events ⇒ ActiveRecord::Relation
Entries for update events.
52 |
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 52 scope :updated_events, -> { where(event: "update") } |