rails_audit_log 1.2.0 → 1.4.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: 40a23f8ea1990a26ea7f575102fb5ddd5f1a7323c796315672de57f0154faec3
4
- data.tar.gz: 8b0563eebeeb21fe9836f3ff091640ae4a1c5484b45dc515abfc4647510cfae9
3
+ metadata.gz: 9e3de1e31a64d33c94042ea45ff4eeb391d48cc65db934bf7b7256656326c480
4
+ data.tar.gz: e34d558b4687f61336af9a7f7e6ae3faebe6ea77533e7da8e360d27dbfc70060
5
5
  SHA512:
6
- metadata.gz: 29cc8682459e2d7968555a49faa77e47c00da42a54bf633f286dd01e89979be8fff6790aa0880de5d7516d23f1fc16f4a88c10b1e4aa086921c6052fdb812784
7
- data.tar.gz: f1a1ba92cfe1c9a1ff0640b9b8a78f94275195fb018da97d89557b9612d631c31293b228e0715d059b790a3f0d2888069066f9897f376e43a40467c8c0c6dbd2
6
+ metadata.gz: cd8d51146c5b915b5c00cc863f23cffc70066cf4cfaafc5e667e9b064b225168b71f45d5699199ba37de78f0089cebabb8a1707f6f837642c32b0fb73c290a6c
7
+ data.tar.gz: 3abf1d5c1894de5496a76b8827eb23f6f8b430e9a4a192505e1012a3be022cf01290b5ef38cc3d9b43e2e4dc5f46c2e2667fdd77015ce0dfb111fab6141530b5
data/README.md CHANGED
@@ -26,6 +26,8 @@ Audit logging for Rails. Tracks `create`, `update`, and `destroy` events as stru
26
26
  - [Time-based retention](#time-based-retention)
27
27
  - [Scheduled and manual pruning](#scheduled-and-manual-pruning)
28
28
  - [Encrypting audit data](#encrypting-audit-data)
29
+ - [Multi-tenancy](#multi-tenancy)
30
+ - [Event streaming](#event-streaming)
29
31
  - [Selective tracking](#selective-tracking)
30
32
  - [Disabling auditing](#disabling-auditing)
31
33
  - [Object reconstruction](#object-reconstruction)
@@ -427,6 +429,131 @@ The generator creates:
427
429
  - `config/initializers/rails_audit_log_encryption.rb` — reads the generated keys from credentials and passes them to `ActiveRecord::Encryption`
428
430
  - `db/migrate/TIMESTAMP_encrypt_rails_audit_log_entries.rb` — re-encrypts existing plain-text audit entries; edit `ENCRYPTED_MODELS` to list your model class names, then run `bin/rails db:migrate`
429
431
 
432
+ ### Multi-tenancy
433
+
434
+ Store the current tenant on every audit entry so queries are naturally isolated per tenant.
435
+
436
+ Run the generator to add the `tenant_id` column:
437
+
438
+ ```bash
439
+ bin/rails generate rails_audit_log:tenant
440
+ bin/rails db:migrate
441
+ ```
442
+
443
+ Set a global resolver in your initializer — the block is called at write time:
444
+
445
+ ```ruby
446
+ # config/initializers/rails_audit_log.rb
447
+ RailsAuditLog.current_tenant { Current.tenant_id }
448
+ ```
449
+
450
+ Or override per model:
451
+
452
+ ```ruby
453
+ class Order < ApplicationRecord
454
+ include RailsAuditLog::Auditable
455
+ audit_log tenant: -> { Current.tenant_id }
456
+ end
457
+ ```
458
+
459
+ The per-model lambda takes precedence over the global resolver. Both accept zero-argument lambdas and store whatever the block returns in the `tenant_id` string column.
460
+
461
+ Scope queries to a single tenant with `for_tenant`:
462
+
463
+ ```ruby
464
+ AuditLogEntry.for_tenant("acme")
465
+ AuditLogEntry.for_tenant(Current.tenant_id).updated_events.since(1.week.ago)
466
+ ```
467
+
468
+ The web dashboard (`/audit`) automatically applies `for_tenant` when `current_tenant` is configured, so entries from other tenants are never exposed.
469
+
470
+ #### Acts As Tenant integration
471
+
472
+ Wire the resolver to `ActsAsTenant` in one line:
473
+
474
+ ```ruby
475
+ # config/initializers/rails_audit_log.rb
476
+ RailsAuditLog.acts_as_tenant!
477
+ ```
478
+
479
+ This is equivalent to `RailsAuditLog.current_tenant { ActsAsTenant.current_tenant&.id }`.
480
+
481
+ ### Event streaming
482
+
483
+ Publish every audit entry to an external consumer as it is written. Set any object implementing `#publish(entry)` as the adapter:
484
+
485
+ ```ruby
486
+ # config/initializers/rails_audit_log.rb
487
+ RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
488
+ ```
489
+
490
+ #### NotificationsAdapter (built-in, zero dependencies)
491
+
492
+ Publishes `rails_audit_log.entry_created` synchronously via `ActiveSupport::Notifications`:
493
+
494
+ ```ruby
495
+ ActiveSupport::Notifications.subscribe("rails_audit_log.entry_created") do |*, payload|
496
+ entry = payload[:entry]
497
+ Rails.logger.info "Audit: #{entry.event} #{entry.item_type}##{entry.item_id}"
498
+ end
499
+ ```
500
+
501
+ #### ActiveJobAdapter (async)
502
+
503
+ Enqueues `PublishEntryJob` so publishing does not block the request. The job fires the same `rails_audit_log.entry_created` notification when performed:
504
+
505
+ ```ruby
506
+ RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new
507
+ # or with a custom queue:
508
+ RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new(queue: :streaming)
509
+ ```
510
+
511
+ #### Custom adapters
512
+
513
+ Any object implementing `#publish(entry)` works — no companion gem needed for Kafka, SQS, or any other transport:
514
+
515
+ ```ruby
516
+ # Kafka via WaterDrop
517
+ class KafkaAuditAdapter
518
+ def initialize(producer: WaterDrop::SyncProducer)
519
+ @producer = producer
520
+ end
521
+
522
+ def publish(entry)
523
+ @producer.call(entry.attributes.to_json, topic: "audit_log")
524
+ end
525
+ end
526
+
527
+ RailsAuditLog.streaming_adapter = KafkaAuditAdapter.new
528
+ ```
529
+
530
+ ```ruby
531
+ # SQS via aws-sdk-sqs
532
+ class SqsAuditAdapter
533
+ def initialize(queue_url:, client: Aws::SQS::Client.new)
534
+ @queue_url = queue_url
535
+ @client = client
536
+ end
537
+
538
+ def publish(entry)
539
+ @client.send_message(
540
+ queue_url: @queue_url,
541
+ message_body: entry.attributes.to_json
542
+ )
543
+ end
544
+ end
545
+
546
+ RailsAuditLog.streaming_adapter = SqsAuditAdapter.new(
547
+ queue_url: ENV.fetch("AUDIT_SQS_URL")
548
+ )
549
+ ```
550
+
551
+ Wrap the adapter in `ActiveJobAdapter` if you want publishing to be asynchronous and not block the request thread.
552
+
553
+ #### Batch mode
554
+
555
+ `batch_audit` flushes the bulk `INSERT` first, then calls `#publish` for each entry individually — streaming consumers receive every entry even in batch mode.
556
+
430
557
  ### Selective tracking
431
558
 
432
559
  Track only specific attributes, or exclude noisy ones:
@@ -31,6 +31,7 @@ module RailsAuditLog
31
31
  class_attribute :_audit_log_retain_for, default: nil
32
32
  class_attribute :_audit_log_async, default: false
33
33
  class_attribute :_audit_log_encrypt, default: nil
34
+ class_attribute :_audit_log_tenant, default: nil
34
35
 
35
36
  _warn_if_audit_table_missing
36
37
 
@@ -119,18 +120,21 @@ module RailsAuditLog
119
120
  # host app to configure +config.active_record.encryption+; decryption is
120
121
  # transparent — {AuditLogEntry#diff}, {AuditLogEntry#reify}, and
121
122
  # {AuditLogEntry.touching} work unchanged for non-SQL access paths
123
+ # @param tenant [Proc, nil] zero-argument lambda evaluated at write time;
124
+ # return value is stored in the +tenant_id+ column; overrides
125
+ # {RailsAuditLog.current_tenant} for this model
122
126
  # @return [void]
123
127
  # @example
124
128
  # class Article < ApplicationRecord
125
129
  # include RailsAuditLog::Auditable
126
130
  # audit_log only: %i[title body published_at],
127
- # meta: { tenant_id: -> { Current.tenant_id } },
131
+ # tenant: -> { Current.tenant_id },
128
132
  # associations: %i[tags],
129
133
  # version_limit: 100,
130
134
  # retain_for: 30.days,
131
135
  # encrypt: true
132
136
  # end
133
- def audit_log(only: nil, ignore: nil, meta: nil, associations: nil, version_limit: nil, retain_for: nil, async: nil, encrypt: nil)
137
+ def audit_log(only: nil, ignore: nil, meta: nil, associations: nil, version_limit: nil, retain_for: nil, async: nil, encrypt: nil, tenant: nil)
134
138
  self._audit_log_only = only.map(&:to_s) if only
135
139
  self._audit_log_ignore = ignore.map(&:to_s) if ignore
136
140
  self._audit_log_meta = meta if meta
@@ -139,6 +143,7 @@ module RailsAuditLog
139
143
  self._audit_log_retain_for = retain_for unless retain_for.nil?
140
144
  self._audit_log_async = async unless async.nil?
141
145
  self._audit_log_encrypt = encrypt unless encrypt.nil?
146
+ self._audit_log_tenant = tenant unless tenant.nil?
142
147
  end
143
148
  end
144
149
 
@@ -183,6 +188,7 @@ module RailsAuditLog
183
188
  object: nil,
184
189
  reason: RailsAuditLog.reason,
185
190
  metadata: meta.presence,
191
+ tenant_id: resolve_tenant_id,
186
192
  whodunnit_snapshot: actor ? RailsAuditLog.whodunnit_display.call(actor) : nil,
187
193
  actor_type: actor&.class&.name,
188
194
  actor_id: actor.respond_to?(:id) ? actor.id : nil
@@ -205,6 +211,7 @@ module RailsAuditLog
205
211
  object: maybe_encrypt(snapshot),
206
212
  reason: RailsAuditLog.reason,
207
213
  metadata: meta.presence,
214
+ tenant_id: resolve_tenant_id,
208
215
  whodunnit_snapshot: actor ? RailsAuditLog.whodunnit_display.call(actor) : nil,
209
216
  actor_type: actor&.class&.name,
210
217
  actor_id: actor.respond_to?(:id) ? actor.id : nil
@@ -227,8 +234,9 @@ module RailsAuditLog
227
234
  period = self.class._audit_log_retain_for || RailsAuditLog.retention_period
228
235
  WriteAuditLogJob.perform_later(entry_attrs.stringify_keys, version_limit: limit, retention_period: period)
229
236
  else
230
- RailsAuditLog::AuditLogEntry.create!(entry_attrs)
237
+ entry = RailsAuditLog::AuditLogEntry.create!(entry_attrs)
231
238
  prune_audit_entries
239
+ RailsAuditLog.publish_entry(entry)
232
240
  end
233
241
  end
234
242
 
@@ -248,6 +256,11 @@ module RailsAuditLog
248
256
  end
249
257
  end
250
258
 
259
+ def resolve_tenant_id
260
+ tenant_proc = self.class._audit_log_tenant || RailsAuditLog.current_tenant
261
+ tenant_proc&.call
262
+ end
263
+
251
264
  def build_audit_metadata
252
265
  meta = {}
253
266
  if self.class._audit_log_meta
@@ -13,5 +13,10 @@ module RailsAuditLog
13
13
 
14
14
  instance_exec(self, &auth) || request_http_basic_authentication("Audit Log")
15
15
  end
16
+
17
+ def base_audit_scope
18
+ tenant_id = RailsAuditLog.current_tenant&.call
19
+ tenant_id ? AuditLogEntry.for_tenant(tenant_id) : AuditLogEntry.all
20
+ end
16
21
  end
17
22
  end
@@ -21,7 +21,7 @@ module RailsAuditLog
21
21
  end
22
22
 
23
23
  def filtered_scope
24
- scope = AuditLogEntry.order(created_at: :desc)
24
+ scope = base_audit_scope.order(created_at: :desc)
25
25
  scope = scope.where(event: @event) if @event
26
26
  scope = scope.where(item_type: @item_type) if @item_type
27
27
  scope = scope.for_period(@period) if @period
@@ -5,7 +5,7 @@ module RailsAuditLog
5
5
  @item_type = params[:item_type]
6
6
  @item_id = params[:item_id]
7
7
  @pagy, @entries = pagy(
8
- AuditLogEntry
8
+ base_audit_scope
9
9
  .where(item_type: @item_type, item_id: @item_id)
10
10
  .order(created_at: :asc)
11
11
  )
@@ -0,0 +1,13 @@
1
+ module RailsAuditLog
2
+ module Streaming
3
+ class PublishEntryJob < ApplicationJob
4
+ def perform(entry_attrs)
5
+ entry = AuditLogEntry.new(entry_attrs)
6
+ ActiveSupport::Notifications.instrument(
7
+ NotificationsAdapter::EVENT,
8
+ entry: entry
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,8 @@
1
1
  module RailsAuditLog
2
2
  class WriteAuditLogJob < ApplicationJob
3
3
  def perform(entry_attrs, version_limit: nil, retention_period: nil)
4
- AuditLogEntry.create!(entry_attrs)
4
+ entry = AuditLogEntry.create!(entry_attrs)
5
+ RailsAuditLog.publish_entry(entry)
5
6
 
6
7
  item_type = entry_attrs["item_type"]
7
8
  item_id = entry_attrs["item_id"]
@@ -91,6 +91,16 @@ module RailsAuditLog
91
91
  end
92
92
  }
93
93
 
94
+ # Entries belonging to a specific tenant.
95
+ # Composable with all other scopes.
96
+ #
97
+ # @param id [String, Integer] the tenant identifier stored in +tenant_id+
98
+ # @return [ActiveRecord::Relation]
99
+ # @example
100
+ # AuditLogEntry.for_tenant("acme")
101
+ # AuditLogEntry.for_tenant(Current.tenant_id).updated_events
102
+ scope :for_tenant, ->(id) { where(tenant_id: id) }
103
+
94
104
  # @!endgroup
95
105
 
96
106
  # @!group Time scopes
@@ -0,0 +1,6 @@
1
+ class AddTenantIdToAuditLogEntries < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ add_column :audit_log_entries, :tenant_id, :string
4
+ add_index :audit_log_entries, :tenant_id
5
+ end
6
+ end
@@ -0,0 +1,33 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module RailsAuditLog
5
+ module Generators
6
+ class TenantGenerator < Rails::Generators::Base
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates a migration that adds a tenant_id column and index to audit_log_entries."
12
+
13
+ def create_migration_file
14
+ migration_template(
15
+ "add_tenant_id_to_audit_log_entries.rb",
16
+ "db/migrate/add_tenant_id_to_audit_log_entries.rb"
17
+ )
18
+ end
19
+
20
+ def print_next_steps
21
+ say ""
22
+ say "Next steps:", :green
23
+ say " 1. Run `bin/rails db:migrate` to add the tenant_id column."
24
+ say " 2. Set a global resolver in your initializer:"
25
+ say " RailsAuditLog.current_tenant { Current.tenant_id }"
26
+ say " or per-model:"
27
+ say " audit_log tenant: -> { Current.tenant_id }"
28
+ say " 3. Use AuditLogEntry.for_tenant(id) to scope queries to a tenant."
29
+ say ""
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module RailsAuditLog
2
+ module Streaming
3
+ # Publishes each audit entry asynchronously by enqueuing
4
+ # {RailsAuditLog::Streaming::PublishEntryJob}. The job fires a
5
+ # +rails_audit_log.entry_created+ notification when performed, so subscribers
6
+ # receive the entry out-of-band without blocking the request.
7
+ #
8
+ # @example
9
+ # RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new
10
+ #
11
+ # # Custom queue:
12
+ # RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::ActiveJobAdapter.new(queue: :streaming)
13
+ class ActiveJobAdapter
14
+ def initialize(queue: nil)
15
+ @queue = queue
16
+ end
17
+
18
+ def publish(entry)
19
+ job = RailsAuditLog::Streaming::PublishEntryJob
20
+ job = job.set(queue: @queue) if @queue
21
+ job.perform_later(entry.attributes.compact)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module RailsAuditLog
2
+ module Streaming
3
+ # Publishes each audit entry synchronously via +ActiveSupport::Notifications+.
4
+ # Zero external dependencies — subscribe with +ActiveSupport::Notifications.subscribe+.
5
+ #
6
+ # @example
7
+ # RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
8
+ #
9
+ # ActiveSupport::Notifications.subscribe("rails_audit_log.entry_created") do |*, payload|
10
+ # entry = payload[:entry]
11
+ # Rails.logger.info "Audit: #{entry.event} on #{entry.item_type}##{entry.item_id}"
12
+ # end
13
+ class NotificationsAdapter
14
+ EVENT = "rails_audit_log.entry_created"
15
+
16
+ def publish(entry)
17
+ ActiveSupport::Notifications.instrument(EVENT, entry: entry)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsAuditLog
2
- VERSION = "1.2.0"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -1,5 +1,7 @@
1
1
  require "rails_audit_log/version"
2
2
  require "rails_audit_log/engine"
3
+ require "rails_audit_log/streaming/notifications_adapter"
4
+ require "rails_audit_log/streaming/active_job_adapter"
3
5
 
4
6
  # RailsAuditLog is a Rails engine that tracks ActiveRecord +create+, +update+,
5
7
  # and +destroy+ events as {AuditLogEntry} records with JSON-first storage and
@@ -90,6 +92,15 @@ module RailsAuditLog
90
92
  # @return [Integer]
91
93
  mattr_accessor :page_size, default: 25
92
94
 
95
+ # The active streaming adapter. Any object implementing +#publish(entry)+.
96
+ # Called after every audit entry is persisted, including batch writes.
97
+ # Set to +nil+ (default) to disable streaming.
98
+ #
99
+ # @return [#publish, nil]
100
+ # @example
101
+ # RailsAuditLog.streaming_adapter = RailsAuditLog::Streaming::NotificationsAdapter.new
102
+ mattr_accessor :streaming_adapter, default: nil
103
+
93
104
  # Controls how an actor object is serialised into the +whodunnit_snapshot+
94
105
  # string column. Defaults to +actor.name+ when available, otherwise +to_s+.
95
106
  #
@@ -114,6 +125,45 @@ module RailsAuditLog
114
125
  yield self
115
126
  end
116
127
 
128
+ # Sets or returns the global tenant resolver block. The block is called at
129
+ # write time and its return value is stored in the +tenant_id+ column of each
130
+ # {AuditLogEntry}. Override per-model with <tt>audit_log tenant: -> { ... }</tt>.
131
+ #
132
+ # @yield block called with no arguments at write time; return the tenant id
133
+ # @return [Proc, nil] the stored block, or +nil+ when not configured
134
+ # @example
135
+ # RailsAuditLog.current_tenant { Current.tenant_id }
136
+ def self.current_tenant(&block)
137
+ @current_tenant = block if block_given?
138
+ @current_tenant
139
+ end
140
+
141
+ # Wires {.current_tenant} to +ActsAsTenant.current_tenant&.id+ so audit
142
+ # entries are automatically scoped to the Acts As Tenant context.
143
+ # Call once in an initializer after the gem is loaded.
144
+ #
145
+ # @raise [RuntimeError] if the +acts_as_tenant+ gem is not loaded
146
+ # @return [void]
147
+ # @example
148
+ # RailsAuditLog.acts_as_tenant!
149
+ def self.acts_as_tenant!
150
+ unless defined?(ActsAsTenant)
151
+ raise "ActsAsTenant is not loaded. Add the `acts_as_tenant` gem to your Gemfile."
152
+ end
153
+
154
+ current_tenant { ActsAsTenant.current_tenant&.id }
155
+ end
156
+
157
+ # Passes +entry+ to the configured {.streaming_adapter} if one is set.
158
+ # No-ops when no adapter is configured.
159
+ #
160
+ # @api private
161
+ # @param entry [AuditLogEntry]
162
+ # @return [void]
163
+ def self.publish_entry(entry)
164
+ streaming_adapter&.publish(entry)
165
+ end
166
+
117
167
  # Sets or returns the authentication block used to gate the web dashboard.
118
168
  # The block is evaluated in controller context, so controller helpers
119
169
  # (e.g. +current_user+) are available directly.
@@ -244,7 +294,10 @@ module RailsAuditLog
244
294
  begin
245
295
  result = yield
246
296
  batch = Thread.current[:rails_audit_log_batch]
247
- AuditLogEntry.insert_all!(batch) if batch.any?
297
+ if batch.any?
298
+ AuditLogEntry.insert_all!(batch)
299
+ batch.each { |attrs| publish_entry(AuditLogEntry.new(attrs)) } if streaming_adapter
300
+ end
248
301
  result
249
302
  ensure
250
303
  Thread.current[:rails_audit_log_batch] = 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: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -99,6 +99,7 @@ files:
99
99
  - app/javascript/rails_audit_log/search_controller.js
100
100
  - app/jobs/rails_audit_log/application_job.rb
101
101
  - app/jobs/rails_audit_log/prune_audit_log_job.rb
102
+ - app/jobs/rails_audit_log/streaming/publish_entry_job.rb
102
103
  - app/jobs/rails_audit_log/write_audit_log_job.rb
103
104
  - app/models/rails_audit_log/application_record.rb
104
105
  - app/models/rails_audit_log/audit_log_entry.rb
@@ -118,11 +119,15 @@ files:
118
119
  - lib/generators/rails_audit_log/install/templates/create_audit_log_entries.rb
119
120
  - lib/generators/rails_audit_log/migrate_from_paper_trail/migrate_from_paper_trail_generator.rb
120
121
  - lib/generators/rails_audit_log/migrate_from_paper_trail/templates/migrate_from_paper_trail.rb
122
+ - lib/generators/rails_audit_log/tenant/templates/add_tenant_id_to_audit_log_entries.rb
123
+ - lib/generators/rails_audit_log/tenant/tenant_generator.rb
121
124
  - lib/rails_audit_log.rb
122
125
  - lib/rails_audit_log/engine.rb
123
126
  - lib/rails_audit_log/matchers.rb
124
127
  - lib/rails_audit_log/minitest_assertions.rb
125
128
  - lib/rails_audit_log/paper_trail_compat.rb
129
+ - lib/rails_audit_log/streaming/active_job_adapter.rb
130
+ - lib/rails_audit_log/streaming/notifications_adapter.rb
126
131
  - lib/rails_audit_log/test_helpers.rb
127
132
  - lib/rails_audit_log/version.rb
128
133
  - lib/tasks/rails_audit_log_tasks.rake