rails_audit_log 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3323e92d5f10ecd832a198a65bf6e0fd65c7bf0ff35069230bc8b7791d14e7fa
4
- data.tar.gz: 4cd882a647d273ee3fe8a6c7b7f89a3b241ee0fa9b22b84aed93b8aea2a30c6d
3
+ metadata.gz: 5cd71899d109aa978910a72ef144aeafb601dc70790e044ef723b49d1d560f65
4
+ data.tar.gz: 8884deec2a0abf63f14639794d6ac614689c9799e09b375ffc0cc7d788943e7d
5
5
  SHA512:
6
- metadata.gz: 7c8d577a7b027d8c300b62545930e94798967f5cdc7f57d4d84a06b3f1968d1357b6ea1db9ae8abf1cb3e36bda8e6fd12be6c2da0798cf7d6d6a02e60849621c
7
- data.tar.gz: 69a905ee623f28403c53d280a56009cd81696406e22ff2f258ef2d0ec56fc4d7a154f72a0a345557ec8a39e0b36b655b3fba7c1a128bee954834e73d537946b6
6
+ metadata.gz: c32ada5189eda30461ea1dbc10a67982fb0ff6c4244da4339aa3376082b38a5272bb3a1b91fe50110c08ae9ee0ae3ede52d2a43701c0f7260a4d5a6581c0f42d
7
+ data.tar.gz: 448becbb80428dd754bca4d1ab43625de5ec85a2c9d712e9e4a28f7c14b0c18ce6865b25dfd9ea6c46f7b1814988d0eea77ae8b6aee7a230927cc37a80f60cf3
data/README.md CHANGED
@@ -23,6 +23,14 @@ bin/rails generate rails_audit_log:install
23
23
  bin/rails db:migrate
24
24
  ```
25
25
 
26
+ Optionally scaffold a commented initializer with every configuration option:
27
+
28
+ ```bash
29
+ bin/rails generate rails_audit_log:initializer
30
+ ```
31
+
32
+ This creates `config/initializers/rails_audit_log.rb` with all settings documented as commented examples inside a `RailsAuditLog.configure` block.
33
+
26
34
  ## Usage
27
35
 
28
36
  ### Tracking a model
@@ -384,6 +392,76 @@ To save storage at the cost of reduced reification accuracy, switch to diff-only
384
392
  RailsAuditLog.store_snapshot = false
385
393
  ```
386
394
 
395
+ ### Test helper
396
+
397
+ `without_audit_log` silences audit tracking inside the block — useful in FactoryBot factories and seed data to avoid noise in the audit trail:
398
+
399
+ ```ruby
400
+ # spec/rails_helper.rb (or spec/support/factory_helpers.rb)
401
+ require "rails_audit_log/test_helpers"
402
+
403
+ RSpec.configure do |config|
404
+ config.include RailsAuditLog::TestHelpers
405
+ end
406
+
407
+ # Or include directly in FactoryBot definitions:
408
+ FactoryBot.define do
409
+ factory :post do
410
+ after(:create) { |p| without_audit_log { p.update!(cached_at: Time.current) } }
411
+ end
412
+ end
413
+ ```
414
+
415
+ `without_audit_log` is a prefix-free wrapper around `RailsAuditLog.disable` — thread-safe and restores tracking even if the block raises.
416
+
417
+ ### Minitest assertions
418
+
419
+ Add to your `test/test_helper.rb`:
420
+
421
+ ```ruby
422
+ require "rails_audit_log/minitest_assertions"
423
+
424
+ class ActiveSupport::TestCase
425
+ include RailsAuditLog::MinitestAssertions
426
+ end
427
+ ```
428
+
429
+ Then use the assertions in any test:
430
+
431
+ ```ruby
432
+ assert_audit_log_entry post # any entry
433
+ assert_audit_log_entry post, event: :update # update entry
434
+ assert_audit_log_entry post, event: :update, touching: :title # touching title
435
+ refute_audit_log_entry post, event: :update # no update entry
436
+ assert_audit_log_entry post, event: :update, message: "custom" # custom failure message
437
+ ```
438
+
439
+ ### RSpec matchers
440
+
441
+ Add to your `spec/rails_helper.rb` (or `spec_helper.rb`):
442
+
443
+ ```ruby
444
+ require "rails_audit_log/matchers"
445
+
446
+ RSpec.configure do |config|
447
+ config.include RailsAuditLog::Matchers
448
+ end
449
+ ```
450
+
451
+ Then use the matchers in any spec:
452
+
453
+ ```ruby
454
+ # Assert a record has a matching audit entry
455
+ expect(post).to have_audit_log_entry
456
+ expect(post).to have_audit_log_entry(:update)
457
+ expect(post).to have_audit_log_entry(:update).touching(:title)
458
+
459
+ # Assert a block creates a matching audit entry
460
+ expect { post.update!(title: "New") }.to create_audit_log_entry
461
+ expect { post.update!(title: "New") }.to create_audit_log_entry(event: :update)
462
+ expect { post.update!(title: "New") }.to create_audit_log_entry(event: :update).touching(:title)
463
+ ```
464
+
387
465
  ## Requirements
388
466
 
389
467
  - Ruby >= 3.3
@@ -10,6 +10,8 @@ module RailsAuditLog
10
10
  class_attribute :_audit_log_version_limit, default: nil
11
11
  class_attribute :_audit_log_async, default: false
12
12
 
13
+ _warn_if_audit_table_missing
14
+
13
15
  has_many :audit_log_entries,
14
16
  class_name: "RailsAuditLog::AuditLogEntry",
15
17
  as: :item,
@@ -52,6 +54,18 @@ module RailsAuditLog
52
54
  end
53
55
 
54
56
  class_methods do
57
+ def _warn_if_audit_table_missing
58
+ return if connection.table_exists?("audit_log_entries")
59
+
60
+ warn "[RailsAuditLog] WARNING: #{name} includes RailsAuditLog::Auditable but the " \
61
+ "'audit_log_entries' table does not exist. " \
62
+ "Run `bin/rails generate rails_audit_log:install && bin/rails db:migrate` to create it."
63
+ rescue ActiveRecord::NoDatabaseError,
64
+ ActiveRecord::ConnectionNotEstablished,
65
+ ActiveRecord::StatementInvalid
66
+ # DB not reachable during this phase (e.g. before db:create) — skip the check
67
+ end
68
+
55
69
  def audit_log(only: nil, ignore: nil, meta: nil, associations: nil, version_limit: nil, async: nil)
56
70
  self._audit_log_only = only.map(&:to_s) if only
57
71
  self._audit_log_ignore = ignore.map(&:to_s) if ignore
@@ -0,0 +1,15 @@
1
+ require "rails/generators"
2
+
3
+ module RailsAuditLog
4
+ module Generators
5
+ class InitializerGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates a commented RailsAuditLog initializer in config/initializers."
9
+
10
+ def create_initializer_file
11
+ template "rails_audit_log.rb", "config/initializers/rails_audit_log.rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ # config/initializers/rails_audit_log.rb
2
+ # Generated by `bin/rails generate rails_audit_log:initializer`
3
+ #
4
+ # All settings are optional. Remove the comments around any line you want to activate.
5
+
6
+ RailsAuditLog.configure do |config|
7
+ # Global columns excluded from all audited models. Default: ["updated_at"]
8
+ # config.ignored_attributes = %w[updated_at cached_at]
9
+
10
+ # Store a full attribute snapshot alongside object_changes. Default: true
11
+ # Disable to save storage; reify and version_at fall back to diff-only reconstruction.
12
+ # config.store_snapshot = false
13
+
14
+ # Capture remote_ip and user_agent into each entry's metadata column. Default: false
15
+ # Requires including RailsAuditLog::Controller in ApplicationController.
16
+ # config.capture_request_metadata = true
17
+
18
+ # Customise how the actor's display name is stored at write time. Default: actor.name
19
+ # config.whodunnit_display = ->(actor) { actor.email }
20
+
21
+ # Global cap on entries retained per tracked record. Default: nil (no limit)
22
+ # Per-model `audit_log version_limit: N` takes precedence.
23
+ # config.version_limit = 100
24
+
25
+ # Write all audit entries asynchronously via WriteAuditLogJob. Default: false
26
+ # Per-model `audit_log async: true` also works.
27
+ # config.async = true
28
+
29
+ # Route AuditLogEntry to a dedicated database (Rails multi-DB). Default: nil
30
+ # config.connects_to = { database: { writing: :audit_log, reading: :audit_log } }
31
+ end
@@ -0,0 +1,103 @@
1
+ module RailsAuditLog
2
+ module Matchers
3
+ def have_audit_log_entry(event = nil)
4
+ HaveAuditLogEntry.new(event)
5
+ end
6
+
7
+ def create_audit_log_entry(event: nil, touching: nil)
8
+ CreateAuditLogEntry.new(event: event, touching: touching)
9
+ end
10
+
11
+ class HaveAuditLogEntry
12
+ def initialize(event)
13
+ @event = event
14
+ @touching = nil
15
+ end
16
+
17
+ def touching(attribute)
18
+ @touching = attribute
19
+ self
20
+ end
21
+
22
+ def matches?(record)
23
+ @record = record
24
+ scope = record.audit_log_entries
25
+ scope = scope.where(event: @event.to_s) if @event
26
+ scope = scope.touching(@touching) if @touching
27
+ scope.exists?
28
+ end
29
+
30
+ def failure_message
31
+ "expected #{@record.class}##{@record.id} to have an audit log entry#{qualifier}"
32
+ end
33
+
34
+ def failure_message_when_negated
35
+ "expected #{@record.class}##{@record.id} not to have an audit log entry#{qualifier}"
36
+ end
37
+
38
+ def description
39
+ "have an audit log entry#{qualifier}"
40
+ end
41
+
42
+ private
43
+
44
+ def qualifier
45
+ parts = []
46
+ parts << " with event '#{@event}'" if @event
47
+ parts << " touching '#{@touching}'" if @touching
48
+ parts.join
49
+ end
50
+ end
51
+
52
+ class CreateAuditLogEntry
53
+ def initialize(event:, touching:)
54
+ @event = event
55
+ @touching = touching
56
+ end
57
+
58
+ def touching(attribute)
59
+ @touching = attribute
60
+ self
61
+ end
62
+
63
+ def supports_block_expectations?
64
+ true
65
+ end
66
+
67
+ def matches?(block)
68
+ @before = matching_scope.count
69
+ block.call
70
+ @after = matching_scope.count
71
+ @after > @before
72
+ end
73
+
74
+ def failure_message
75
+ "expected block to create an audit log entry#{qualifier}, but none was created"
76
+ end
77
+
78
+ def failure_message_when_negated
79
+ "expected block not to create an audit log entry#{qualifier}, but one was created"
80
+ end
81
+
82
+ def description
83
+ "create an audit log entry#{qualifier}"
84
+ end
85
+
86
+ private
87
+
88
+ def matching_scope
89
+ scope = RailsAuditLog::AuditLogEntry.all
90
+ scope = scope.where(event: @event.to_s) if @event
91
+ scope = scope.touching(@touching) if @touching
92
+ scope
93
+ end
94
+
95
+ def qualifier
96
+ parts = []
97
+ parts << " with event '#{@event}'" if @event
98
+ parts << " touching '#{@touching}'" if @touching
99
+ parts.join
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,31 @@
1
+ module RailsAuditLog
2
+ module MinitestAssertions
3
+ def assert_audit_log_entry(record, event: nil, touching: nil, message: nil)
4
+ scope = build_scope(record, event, touching)
5
+ msg = message || default_message("to have", record, event, touching)
6
+ assert scope.exists?, msg
7
+ end
8
+
9
+ def refute_audit_log_entry(record, event: nil, touching: nil, message: nil)
10
+ scope = build_scope(record, event, touching)
11
+ msg = message || default_message("not to have", record, event, touching)
12
+ refute scope.exists?, msg
13
+ end
14
+
15
+ private
16
+
17
+ def build_scope(record, event, touching)
18
+ scope = record.audit_log_entries
19
+ scope = scope.where(event: event.to_s) if event
20
+ scope = scope.touching(touching) if touching
21
+ scope
22
+ end
23
+
24
+ def default_message(polarity, record, event, touching)
25
+ parts = []
26
+ parts << " with event '#{event}'" if event
27
+ parts << " touching '#{touching}'" if touching
28
+ "Expected #{record.class}##{record.id} #{polarity} an audit log entry#{parts.join}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ module RailsAuditLog
2
+ module TestHelpers
3
+ def without_audit_log(&block)
4
+ RailsAuditLog.disable(&block)
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsAuditLog
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -5,6 +5,10 @@ module RailsAuditLog
5
5
  # Global default columns to ignore across all audited models.
6
6
  # Override in an initializer: RailsAuditLog.ignored_attributes = %w[updated_at cached_at]
7
7
  mattr_accessor :ignored_attributes, default: %w[updated_at]
8
+
9
+ def self.configure
10
+ yield self
11
+ end
8
12
  mattr_accessor :store_snapshot, default: true
9
13
  mattr_accessor :capture_request_metadata, default: false
10
14
  mattr_accessor :version_limit, default: nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_audit_log
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -46,10 +46,15 @@ files:
46
46
  - app/models/rails_audit_log/audit_log_entry.rb
47
47
  - app/views/layouts/rails_audit_log/application.html.erb
48
48
  - config/routes.rb
49
+ - lib/generators/rails_audit_log/initializer/initializer_generator.rb
50
+ - lib/generators/rails_audit_log/initializer/templates/rails_audit_log.rb
49
51
  - lib/generators/rails_audit_log/install/install_generator.rb
50
52
  - lib/generators/rails_audit_log/install/templates/create_audit_log_entries.rb
51
53
  - lib/rails_audit_log.rb
52
54
  - lib/rails_audit_log/engine.rb
55
+ - lib/rails_audit_log/matchers.rb
56
+ - lib/rails_audit_log/minitest_assertions.rb
57
+ - lib/rails_audit_log/test_helpers.rb
53
58
  - lib/rails_audit_log/version.rb
54
59
  - lib/tasks/rails_audit_log_tasks.rake
55
60
  homepage: https://github.com/eclectic-coding/rails_audit_log