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/encryption/encryption_generator.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.2.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. -
#encrypt ⇒ Boolean
When
true, encryptsobject_changesandobjectfor all audited models usingActiveRecord::Encryption. -
#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).
148 149 150 |
# File 'lib/rails_audit_log.rb', line 148 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.
157 158 159 |
# File 'lib/rails_audit_log.rb', line 157 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.
220 221 222 223 224 225 226 |
# File 'lib/rails_audit_log.rb', line 220 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.
126 127 128 129 |
# File 'lib/rails_audit_log.rb', line 126 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.
240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/rails_audit_log.rb', line 240 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.
259 260 261 |
# File 'lib/rails_audit_log.rb', line 259 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.
113 114 115 |
# File 'lib/rails_audit_log.rb', line 113 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.
191 192 193 194 195 196 197 |
# File 'lib/rails_audit_log.rb', line 191 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.
180 181 182 |
# File 'lib/rails_audit_log.rb', line 180 def self.enabled? !Thread.current[:rails_audit_log_disabled] end |
.reason ⇒ String?
Returns the reason string set on the current thread.
202 203 204 |
# File 'lib/rails_audit_log.rb', line 202 def self.reason Thread.current[:rails_audit_log_reason] end |
.reason=(value) ⇒ String?
208 209 210 |
# File 'lib/rails_audit_log.rb', line 208 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.
135 136 137 |
# File 'lib/rails_audit_log.rb', line 135 def self. Thread.current[:rails_audit_log_request_metadata] end |
.request_metadata=(value) ⇒ Hash?
141 142 143 |
# File 'lib/rails_audit_log.rb', line 141 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.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/rails_audit_log.rb', line 277 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.
169 170 171 172 173 174 175 |
# File 'lib/rails_audit_log.rb', line 169 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.
86 |
# File 'lib/rails_audit_log.rb', line 86 mattr_accessor :connects_to, default: nil |
#encrypt ⇒ Boolean
When true, encrypts object_changes and object for all audited models using ActiveRecord::Encryption. Requires the host app to configure config.active_record.encryption. Override per-model with audit_log encrypt: false to opt a specific model out.
78 |
# File 'lib/rails_audit_log.rb', line 78 mattr_accessor :encrypt, default: false |
#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.
91 |
# File 'lib/rails_audit_log.rb', line 91 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.
99 100 101 |
# File 'lib/rails_audit_log.rb', line 99 mattr_accessor :whodunnit_display, default: ->(actor) { actor.respond_to?(:name) ? actor.name.to_s : actor.to_s } |