Class: RailsAuditLog::AuditLogEntry

Inherits:
ApplicationRecord show all
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: create stores [nil, new] for every column, update stores [old, new] for changed columns only, destroy stores [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

Event scopes collapse

Actor / resource scopes collapse

Time scopes collapse

Class Method Summary collapse

Instance Method Summary collapse

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.



29
30
31
32
33
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 29

def self.configure_connection!
  return unless (opts = RailsAuditLog.connects_to)

  connects_to(**opts)
end

Instance Method Details

#by_actorActiveRecord::Relation

Entries written by a specific actor.

Examples:

AuditLogEntry.by_actor(current_user)

Parameters:

  • actor (ActiveRecord::Base)

    the actor record to filter by

Returns:

  • (ActiveRecord::Relation)


74
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 74

scope :by_actor, ->(actor) { where(actor_type: actor.class.name, actor_id: actor.id) }

#changed_attributesArray<String>

Returns the list of attribute (and association) names that changed in this entry, derived from the keys of object_changes.

Returns:

  • (Array<String>)


199
200
201
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 199

def changed_attributes
  object_changes&.keys || []
end

#created_eventsActiveRecord::Relation

Entries for create events.

Returns:

  • (ActiveRecord::Relation)


47
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 47

scope :created_events,  -> { where(event: "create") }

#destroyed_eventsActiveRecord::Relation

Entries for destroy events.

Returns:

  • (ActiveRecord::Relation)


55
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 55

scope :destroyed_events, -> { where(event: "destroy") }

#diffHash{String => Hash}

Returns object_changes in a named-key format convenient for display.

Examples:

entry.diff
# => { "title" => { from: "Old", to: "New" }, ... }

Returns:

  • (Hash{String => Hash})

    keys are attribute names; values are hashes with :from and :to keys



210
211
212
213
214
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 210

def diff
  return {} unless object_changes

  object_changes.transform_values { |from_to| { from: from_to[0], to: from_to[1] } }
end

#for_periodActiveRecord::Relation

Entries within a named period. Valid keys: "1h", "24h", "7d".

Parameters:

  • period (String)

    one of PERIODS.keys

Returns:

  • (ActiveRecord::Relation)


113
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 113

scope :for_period, ->(period) { where(created_at: PERIODS[period].ago..) }

#for_resourceActiveRecord::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.

Examples:

All entries for Article

AuditLogEntry.for_resource(Article)

All entries for one article

AuditLogEntry.for_resource(article)

Parameters:

  • resource (Class, ActiveRecord::Base)

Returns:

  • (ActiveRecord::Relation)


85
86
87
88
89
90
91
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 85

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
}

#nextAuditLogEntry?

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.

Returns:



191
192
193
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 191

def next
  self.class.where(item_type: item_type, item_id: item_id).where("id > ?", id).order(id: :asc).first
end

#previousAuditLogEntry?

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.

Returns:



183
184
185
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 183

def previous
  self.class.where(item_type: item_type, item_id: item_id).where("id < ?", id).order(id: :desc).first
end

#reifyActiveRecord::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).

Returns:

  • (ActiveRecord::Base, nil)

    an unpersisted instance; nil for create entries (there is no prior state)



148
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
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 148

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

#sinceActiveRecord::Relation

Entries created at or after time.

Parameters:

  • time (Time)

Returns:

  • (ActiveRecord::Relation)


101
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 101

scope :since, ->(time) { where(created_at: time..) }

#slimActiveRecord::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.

Returns:

  • (ActiveRecord::Relation)


122
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 122

scope :slim, -> { select(column_names - BLOB_COLUMNS) }

#touchingActiveRecord::Relation

Entries where object_changes contains a key matching attribute. Uses json_extract on SQLite/MySQL and ->> on PostgreSQL.

Examples:

AuditLogEntry.touching(:title)
post.audit_log_entries.updated_events.touching(:published_at)

Parameters:

  • attribute (Symbol, String)

    the attribute name to filter on

Returns:

  • (ActiveRecord::Relation)


132
133
134
135
136
137
138
139
140
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 132

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
}

#untilActiveRecord::Relation

Entries created at or before time.

Parameters:

  • time (Time)

Returns:

  • (ActiveRecord::Relation)


107
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 107

scope :until, ->(time) { where(created_at: ..time) }

#updated_eventsActiveRecord::Relation

Entries for update events.

Returns:

  • (ActiveRecord::Relation)


51
# File 'app/models/rails_audit_log/audit_log_entry.rb', line 51

scope :updated_events,  -> { where(event: "update") }