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/tenant/tenant_generator.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.3.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.
-
.acts_as_tenant! ⇒ void
Wires RailsAuditLog.current_tenant to ActsAsTenant.current_tenant&.id so audit entries are automatically scoped to the Acts As Tenant context.
-
.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. -
.current_tenant { ... } ⇒ Proc?
Sets or returns the global tenant resolver block.
-
.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).
177 178 179 |
# File 'lib/rails_audit_log.rb', line 177 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.
186 187 188 |
# File 'lib/rails_audit_log.rb', line 186 def self.actor=(actor) Thread.current[:rails_audit_log_actor] = actor end |
.acts_as_tenant! ⇒ void
This method returns an undefined value.
Wires current_tenant to ActsAsTenant.current_tenant&.id so audit entries are automatically scoped to the Acts As Tenant context. Call once in an initializer after the gem is loaded.
138 139 140 141 142 143 144 |
# File 'lib/rails_audit_log.rb', line 138 def self.acts_as_tenant! unless defined?(ActsAsTenant) raise "ActsAsTenant is not loaded. Add the `acts_as_tenant` gem to your Gemfile." end current_tenant { ActsAsTenant.current_tenant&.id } 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.
249 250 251 252 253 254 255 |
# File 'lib/rails_audit_log.rb', line 249 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.
155 156 157 158 |
# File 'lib/rails_audit_log.rb', line 155 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.
269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/rails_audit_log.rb', line 269 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.
288 289 290 |
# File 'lib/rails_audit_log.rb', line 288 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 |
.current_tenant { ... } ⇒ Proc?
Sets or returns the global tenant resolver block. The block is called at write time and its return value is stored in the tenant_id column of each AuditLogEntry. Override per-model with audit_log tenant: -> { ... }.
125 126 127 128 |
# File 'lib/rails_audit_log.rb', line 125 def self.current_tenant(&block) @current_tenant = block if block_given? @current_tenant 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.
220 221 222 223 224 225 226 |
# File 'lib/rails_audit_log.rb', line 220 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.
209 210 211 |
# File 'lib/rails_audit_log.rb', line 209 def self.enabled? !Thread.current[:rails_audit_log_disabled] end |
.reason ⇒ String?
Returns the reason string set on the current thread.
231 232 233 |
# File 'lib/rails_audit_log.rb', line 231 def self.reason Thread.current[:rails_audit_log_reason] end |
.reason=(value) ⇒ String?
237 238 239 |
# File 'lib/rails_audit_log.rb', line 237 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.
164 165 166 |
# File 'lib/rails_audit_log.rb', line 164 def self. Thread.current[:rails_audit_log_request_metadata] end |
.request_metadata=(value) ⇒ Hash?
170 171 172 |
# File 'lib/rails_audit_log.rb', line 170 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.
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/rails_audit_log.rb', line 306 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.
198 199 200 201 202 203 204 |
# File 'lib/rails_audit_log.rb', line 198 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 } |