Module: RailsAuditLog
- Defined in:
- lib/rails_audit_log.rb,
lib/rails_audit_log/engine.rb,
lib/rails_audit_log/version.rb,
lib/rails_audit_log/matchers.rb,
lib/rails_audit_log/test_helpers.rb,
app/concerns/rails_audit_log/auditable.rb,
lib/rails_audit_log/paper_trail_compat.rb,
app/concerns/rails_audit_log/controller.rb,
lib/rails_audit_log/minitest_assertions.rb,
app/jobs/rails_audit_log/application_job.rb,
app/models/rails_audit_log/audit_log_entry.rb,
app/jobs/rails_audit_log/prune_audit_log_job.rb,
app/jobs/rails_audit_log/write_audit_log_job.rb,
app/models/rails_audit_log/application_record.rb,
app/helpers/rails_audit_log/application_helper.rb,
app/controllers/rails_audit_log/resources_controller.rb,
app/controllers/rails_audit_log/application_controller.rb,
lib/generators/rails_audit_log/install/install_generator.rb,
app/controllers/rails_audit_log/audit_log_entries_controller.rb,
lib/generators/rails_audit_log/initializer/initializer_generator.rb,
lib/generators/rails_audit_log/migrate_from_paper_trail/migrate_from_paper_trail_generator.rb
Overview
RailsAuditLog is a Rails engine that tracks ActiveRecord create, update, and destroy events as AuditLogEntry records with JSON-first storage and thread-local actor context.
Quick start
# config/initializers/rails_audit_log.rb
RailsAuditLog.configure do |config|
config.ignored_attributes = %w[updated_at cached_at]
config.store_snapshot = true
config.async = false
end
# app/models/article.rb
class Article < ApplicationRecord
include RailsAuditLog::Auditable
audit_log only: %i[title body]
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include RailsAuditLog::Controller
audit_log_actor { current_user }
end
Defined Under Namespace
Modules: ApplicationHelper, Auditable, Controller, Generators, Matchers, MinitestAssertions, PaperTrailCompat, TestHelpers Classes: ApplicationController, ApplicationJob, ApplicationRecord, AuditLogEntriesController, AuditLogEntry, Engine, PruneAuditLogJob, ResourcesController, WriteAuditLogJob
Constant Summary collapse
- VERSION =
"1.1.0"
Class Method Summary collapse
-
.actor ⇒ Object?
Returns the actor set on the current thread (e.g. the signed-in user).
-
.actor=(actor) ⇒ Object?
Sets the actor on the current thread.
-
.audit_log_reason(value) { ... } ⇒ Object
Sets a human-readable reason for the changes made within the block.
-
.authenticate { ... } ⇒ Proc?
Sets or returns the authentication block used to gate the web dashboard.
-
.batch_audit { ... } ⇒ Object
Collects all AuditLogEntry records created within the block and inserts them with a single <tt>INSERT …
-
.batch_audit_buffer ⇒ Array<Hash>?
private
Returns the in-progress batch buffer for the current thread, or
nilwhen no batch is active. -
.configure {|RailsAuditLog| ... } ⇒ void
Yields the module so every
mattr_accessorsetter is reachable asconfig.setting = value. -
.disable { ... } ⇒ Object
Suspends audit logging for the duration of the block on the current thread.
-
.enabled? ⇒ Boolean
Returns
truewhen audit logging is active on the current thread. -
.reason ⇒ String?
Returns the reason string set on the current thread.
- .reason=(value) ⇒ String?
-
.request_metadata ⇒ Hash?
Returns the request metadata hash attached to the current thread.
- .request_metadata=(value) ⇒ Hash?
-
.version_at(record, time) ⇒ ActiveRecord::Base?
Reconstructs the state of
recordas it was attimeby replaying audit entries up to that timestamp. -
.with_actor(actor) { ... } ⇒ Object
Sets the actor for the duration of the block, then restores the previous value.
Instance Method Summary collapse
-
#async ⇒ Boolean
When
true, all audit writes are dispatched viaWriteAuditLogJobinstead of being written inline. -
#capture_request_metadata ⇒ Boolean
When
true, capturesremote_ipanduser_agentfrom the current request and merges them into every entry’smetadatacolumn. -
#connects_to ⇒ Hash?
Passes
connects_tooptions directly to AuditLogEntry so audit entries can be stored on a separate database. -
#ignored_attributes ⇒ Array<String>
Columns ignored on every audited model unless overridden with
only:orignore:on Auditable.audit_log. -
#page_size ⇒ Integer
Number of entries per page in the web dashboard.
-
#retention_period ⇒ ActiveSupport::Duration?
Global time-based TTL for audit entries.
-
#store_snapshot ⇒ Boolean
Whether to store a full snapshot of the record’s attributes in the
objectcolumn alongsideobject_changes. -
#version_limit ⇒ Integer?
Global cap on the number of AuditLogEntry records kept per tracked object.
-
#whodunnit_display ⇒ Proc
Controls how an actor object is serialised into the
whodunnit_snapshotstring column.
Class Method Details
.actor ⇒ Object?
Returns the actor set on the current thread (e.g. the signed-in user).
140 141 142 |
# File 'lib/rails_audit_log.rb', line 140 def self.actor Thread.current[:rails_audit_log_actor] end |
.actor=(actor) ⇒ Object?
Sets the actor on the current thread. Prefer with_actor for scoped assignment so the value is always restored.
149 150 151 |
# File 'lib/rails_audit_log.rb', line 149 def self.actor=(actor) Thread.current[:rails_audit_log_actor] = actor end |
.audit_log_reason(value) { ... } ⇒ Object
Sets a human-readable reason for the changes made within the block. The reason is stored in each AuditLogEntry#reason and restored afterwards.
212 213 214 215 216 217 218 |
# File 'lib/rails_audit_log.rb', line 212 def self.audit_log_reason(value) previous = self.reason self.reason = value yield ensure self.reason = previous end |
.authenticate { ... } ⇒ Proc?
Sets or returns the authentication block used to gate the web dashboard. The block is evaluated in controller context, so controller helpers (e.g. current_user) are available directly. When the block returns falsy, the engine falls back to HTTP Basic auth.
118 119 120 121 |
# File 'lib/rails_audit_log.rb', line 118 def self.authenticate(&block) @authenticate = block if block_given? @authenticate end |
.batch_audit { ... } ⇒ Object
Collects all AuditLogEntry records created within the block and inserts them with a single INSERT ... VALUES (…), (…) via insert_all! instead of one INSERT per record.
Calls are idempotent: if a batch is already in progress on the current thread (i.e. a nested call), the inner block joins the outer batch.
232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/rails_audit_log.rb', line 232 def self.batch_audit return yield if Thread.current[:rails_audit_log_batch] Thread.current[:rails_audit_log_batch] = [] begin result = yield batch = Thread.current[:rails_audit_log_batch] AuditLogEntry.insert_all!(batch) if batch.any? result ensure Thread.current[:rails_audit_log_batch] = nil end end |
.batch_audit_buffer ⇒ Array<Hash>?
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.
Returns the in-progress batch buffer for the current thread, or nil when no batch is active.
251 252 253 |
# File 'lib/rails_audit_log.rb', line 251 def self.batch_audit_buffer Thread.current[:rails_audit_log_batch] end |
.configure {|RailsAuditLog| ... } ⇒ void
This method returns an undefined value.
Yields the module so every mattr_accessor setter is reachable as config.setting = value.
105 106 107 |
# File 'lib/rails_audit_log.rb', line 105 def self.configure yield self end |
.disable { ... } ⇒ Object
Suspends audit logging for the duration of the block on the current thread. Useful in seeds, factories, and test setup where audit noise is unwanted.
183 184 185 186 187 188 189 |
# File 'lib/rails_audit_log.rb', line 183 def self.disable previous = Thread.current[:rails_audit_log_disabled] Thread.current[:rails_audit_log_disabled] = true yield ensure Thread.current[:rails_audit_log_disabled] = previous end |
.enabled? ⇒ Boolean
Returns true when audit logging is active on the current thread.
172 173 174 |
# File 'lib/rails_audit_log.rb', line 172 def self.enabled? !Thread.current[:rails_audit_log_disabled] end |
.reason ⇒ String?
Returns the reason string set on the current thread.
194 195 196 |
# File 'lib/rails_audit_log.rb', line 194 def self.reason Thread.current[:rails_audit_log_reason] end |
.reason=(value) ⇒ String?
200 201 202 |
# File 'lib/rails_audit_log.rb', line 200 def self.reason=(value) Thread.current[:rails_audit_log_reason] = value end |
.request_metadata ⇒ Hash?
Returns the request metadata hash attached to the current thread. Populated by Controller when #capture_request_metadata is true.
127 128 129 |
# File 'lib/rails_audit_log.rb', line 127 def self. Thread.current[:rails_audit_log_request_metadata] end |
.request_metadata=(value) ⇒ Hash?
133 134 135 |
# File 'lib/rails_audit_log.rb', line 133 def self.(value) Thread.current[:rails_audit_log_request_metadata] = value end |
.version_at(record, time) ⇒ ActiveRecord::Base?
Reconstructs the state of record as it was at time by replaying audit entries up to that timestamp.
Returns an unsaved, non-persisted instance of record.class whose attributes match the record’s state at time, or nil when no audit entry exists before time or the record was destroyed at or before time.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/rails_audit_log.rb', line 269 def self.version_at(record, time) entry = AuditLogEntry .where(item_type: record.class.name, item_id: record.id) .where(created_at: ..time) .order(created_at: :desc, id: :desc) .first return nil if entry.nil? || entry.event == "destroy" klass = record.class column_names = klass.column_names.map(&:to_s) to_attrs = (entry.object_changes || {}) .select { |k, _| column_names.include?(k) } .transform_values { |v| v[1] } attrs = entry.object.present? ? entry.object.merge(to_attrs) : to_attrs instance = klass.new instance.assign_attributes(attrs.except("id")) instance.id = attrs.fetch("id") { entry.item_id } instance end |
.with_actor(actor) { ... } ⇒ Object
Sets the actor for the duration of the block, then restores the previous value. Use this in background jobs and rake tasks.
161 162 163 164 165 166 167 |
# File 'lib/rails_audit_log.rb', line 161 def self.with_actor(actor) previous = self.actor self.actor = actor yield ensure self.actor = previous end |
Instance Method Details
#async ⇒ Boolean
When true, all audit writes are dispatched via WriteAuditLogJob instead of being written inline. Override per-model with audit_log async: true.
70 |
# File 'lib/rails_audit_log.rb', line 70 mattr_accessor :async, default: false |
#capture_request_metadata ⇒ Boolean
When true, captures remote_ip and user_agent from the current request and merges them into every entry’s metadata column. Requires Controller to be included in your base controller.
47 |
# File 'lib/rails_audit_log.rb', line 47 mattr_accessor :capture_request_metadata, default: false |
#connects_to ⇒ Hash?
Passes connects_to options directly to AuditLogEntry so audit entries can be stored on a separate database.
78 |
# File 'lib/rails_audit_log.rb', line 78 mattr_accessor :connects_to, default: nil |
#ignored_attributes ⇒ Array<String>
Columns ignored on every audited model unless overridden with only: or ignore: on Auditable.audit_log.
33 |
# File 'lib/rails_audit_log.rb', line 33 mattr_accessor :ignored_attributes, default: %w[updated_at] |
#page_size ⇒ Integer
Number of entries per page in the web dashboard.
83 |
# File 'lib/rails_audit_log.rb', line 83 mattr_accessor :page_size, default: 25 |
#retention_period ⇒ ActiveSupport::Duration?
Global time-based TTL for audit entries. Entries whose created_at is older than this duration are pruned automatically after each write. Composes with #version_limit — an entry is removed when it exceeds either constraint.
64 |
# File 'lib/rails_audit_log.rb', line 64 mattr_accessor :retention_period, default: nil |
#store_snapshot ⇒ Boolean
Whether to store a full snapshot of the record’s attributes in the object column alongside object_changes. Disable to reduce storage at the cost of losing RailsAuditLog::AuditLogEntry#reify fidelity for pre-snapshot entries.
40 |
# File 'lib/rails_audit_log.rb', line 40 mattr_accessor :store_snapshot, default: true |
#version_limit ⇒ Integer?
Global cap on the number of AuditLogEntry records kept per tracked object. Oldest entries are pruned after each write once the limit is exceeded. Override per-model with audit_log version_limit: N.
54 |
# File 'lib/rails_audit_log.rb', line 54 mattr_accessor :version_limit, default: nil |
#whodunnit_display ⇒ Proc
Controls how an actor object is serialised into the whodunnit_snapshot string column. Defaults to actor.name when available, otherwise to_s.
91 92 93 |
# File 'lib/rails_audit_log.rb', line 91 mattr_accessor :whodunnit_display, default: ->(actor) { actor.respond_to?(:name) ? actor.name.to_s : actor.to_s } |