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/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, ResourcesController, WriteAuditLogJob
Constant Summary collapse
- VERSION =
"1.0.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.
-
#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).
130 131 132 |
# File 'lib/rails_audit_log.rb', line 130 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.
139 140 141 |
# File 'lib/rails_audit_log.rb', line 139 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.
202 203 204 205 206 207 208 |
# File 'lib/rails_audit_log.rb', line 202 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.
108 109 110 111 |
# File 'lib/rails_audit_log.rb', line 108 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.
222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/rails_audit_log.rb', line 222 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.
241 242 243 |
# File 'lib/rails_audit_log.rb', line 241 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.
95 96 97 |
# File 'lib/rails_audit_log.rb', line 95 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.
173 174 175 176 177 178 179 |
# File 'lib/rails_audit_log.rb', line 173 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.
162 163 164 |
# File 'lib/rails_audit_log.rb', line 162 def self.enabled? !Thread.current[:rails_audit_log_disabled] end |
.reason ⇒ String?
Returns the reason string set on the current thread.
184 185 186 |
# File 'lib/rails_audit_log.rb', line 184 def self.reason Thread.current[:rails_audit_log_reason] end |
.reason=(value) ⇒ String?
190 191 192 |
# File 'lib/rails_audit_log.rb', line 190 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.
117 118 119 |
# File 'lib/rails_audit_log.rb', line 117 def self. Thread.current[:rails_audit_log_request_metadata] end |
.request_metadata=(value) ⇒ Hash?
123 124 125 |
# File 'lib/rails_audit_log.rb', line 123 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.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/rails_audit_log.rb', line 259 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.
151 152 153 154 155 156 157 |
# File 'lib/rails_audit_log.rb', line 151 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.
60 |
# File 'lib/rails_audit_log.rb', line 60 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.
68 |
# File 'lib/rails_audit_log.rb', line 68 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.
73 |
# File 'lib/rails_audit_log.rb', line 73 mattr_accessor :page_size, default: 25 |
#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.
81 82 83 |
# File 'lib/rails_audit_log.rb', line 81 mattr_accessor :whodunnit_display, default: ->(actor) { actor.respond_to?(:name) ? actor.name.to_s : actor.to_s } |