tangledwires-audited 6.0.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 +7 -0
- data/Appraisals +37 -0
- data/CHANGELOG.md +539 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +447 -0
- data/Rakefile +16 -0
- data/audited.gemspec +38 -0
- data/lib/audited/audit.rb +204 -0
- data/lib/audited/audit_associate.rb +8 -0
- data/lib/audited/auditor.rb +564 -0
- data/lib/audited/railtie.rb +16 -0
- data/lib/audited/rspec_matchers.rb +228 -0
- data/lib/audited/sweeper.rb +42 -0
- data/lib/audited/version.rb +5 -0
- data/lib/audited-rspec.rb +6 -0
- data/lib/audited.rb +60 -0
- data/lib/generators/audited/install_generator.rb +27 -0
- data/lib/generators/audited/migration.rb +25 -0
- data/lib/generators/audited/migration_helper.rb +11 -0
- data/lib/generators/audited/templates/add_association_to_audits.rb +13 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +11 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +12 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +12 -0
- data/lib/generators/audited/templates/add_version_to_auditable_index.rb +23 -0
- data/lib/generators/audited/templates/create_audit_associates.rb +26 -0
- data/lib/generators/audited/templates/install.rb +39 -0
- data/lib/generators/audited/templates/rename_association_to_associated.rb +25 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +11 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +13 -0
- data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +22 -0
- data/lib/generators/audited/upgrade_generator.rb +74 -0
- data/shell.nix +8 -0
- metadata +241 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Audited
|
|
4
|
+
# Specify this act if you want changes to your model to be saved in an
|
|
5
|
+
# audit table. This assumes there is an audits table ready.
|
|
6
|
+
#
|
|
7
|
+
# class User < ActiveRecord::Base
|
|
8
|
+
# audited
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# To store an audit comment set model.audit_comment to your comment before
|
|
12
|
+
# a create, update or destroy operation.
|
|
13
|
+
#
|
|
14
|
+
# See <tt>Audited::Auditor::ClassMethods#audited</tt>
|
|
15
|
+
# for configuration options
|
|
16
|
+
module Auditor # :nodoc:
|
|
17
|
+
extend ActiveSupport::Concern
|
|
18
|
+
|
|
19
|
+
CALLBACKS = [:audit_create, :audit_update, :audit_destroy]
|
|
20
|
+
|
|
21
|
+
module ClassMethods
|
|
22
|
+
# == Configuration options
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
# * +only+ - Only audit the given attributes
|
|
26
|
+
# * +except+ - Excludes fields from being saved in the audit log.
|
|
27
|
+
# By default, Audited will audit all but these fields:
|
|
28
|
+
#
|
|
29
|
+
# [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
|
|
30
|
+
# You can add to those by passing one or an array of fields to skip.
|
|
31
|
+
#
|
|
32
|
+
# class User < ActiveRecord::Base
|
|
33
|
+
# audited except: :password
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# * +require_comment+ - Ensures that audit_comment is supplied before
|
|
37
|
+
# any create, update or destroy operation.
|
|
38
|
+
# * +max_audits+ - Limits the number of stored audits.
|
|
39
|
+
|
|
40
|
+
# * +redacted+ - Changes to these fields will be logged, but the values
|
|
41
|
+
# will not. This is useful, for example, if you wish to audit when a
|
|
42
|
+
# password is changed, without saving the actual password in the log.
|
|
43
|
+
# To store values as something other than '[REDACTED]', pass an argument
|
|
44
|
+
# to the redaction_value option.
|
|
45
|
+
#
|
|
46
|
+
# class User < ActiveRecord::Base
|
|
47
|
+
# audited redacted: :password, redaction_value: SecureRandom.uuid
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# * +if+ - Only audit the model when the given function returns true
|
|
51
|
+
# * +unless+ - Only audit the model when the given function returns false
|
|
52
|
+
#
|
|
53
|
+
# class User < ActiveRecord::Base
|
|
54
|
+
# audited :if => :active?
|
|
55
|
+
#
|
|
56
|
+
# def active?
|
|
57
|
+
# self.status == 'active'
|
|
58
|
+
# end
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
def audited(options = {})
|
|
62
|
+
audited? ? update_audited_options(options) : set_audit(options)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def audited?
|
|
68
|
+
included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_audit(options)
|
|
72
|
+
extend Audited::Auditor::AuditedClassMethods
|
|
73
|
+
include Audited::Auditor::AuditedInstanceMethods
|
|
74
|
+
|
|
75
|
+
class_attribute :audit_associated_with, instance_writer: false
|
|
76
|
+
class_attribute :audited_options, instance_writer: false
|
|
77
|
+
attr_accessor :audit_version, :audit_comment
|
|
78
|
+
|
|
79
|
+
set_audited_options(options)
|
|
80
|
+
|
|
81
|
+
if audited_options[:comment_required]
|
|
82
|
+
validate :presence_of_audit_comment
|
|
83
|
+
before_destroy :require_comment if audited_options[:on].include?(:destroy)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name, inverse_of: :auditable
|
|
87
|
+
Audited.audit_class.audited_class_names << to_s
|
|
88
|
+
|
|
89
|
+
after_create :audit_create if audited_options[:on].include?(:create)
|
|
90
|
+
before_update :audit_update if audited_options[:on].include?(:update)
|
|
91
|
+
after_touch :audit_touch if audited_options[:on].include?(:touch) && ::ActiveRecord::VERSION::MAJOR >= 6
|
|
92
|
+
before_destroy :audit_destroy if audited_options[:on].include?(:destroy)
|
|
93
|
+
|
|
94
|
+
# Define and set after_audit and around_audit callbacks. This might be useful if you want
|
|
95
|
+
# to notify a party after the audit has been created or if you want to access the newly-created
|
|
96
|
+
# audit.
|
|
97
|
+
define_callbacks :audit
|
|
98
|
+
set_callback :audit, :after, :after_audit, if: lambda { respond_to?(:after_audit, true) }
|
|
99
|
+
set_callback :audit, :around, :around_audit, if: lambda { respond_to?(:around_audit, true) }
|
|
100
|
+
|
|
101
|
+
enable_auditing
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def has_associated_audits
|
|
105
|
+
has_many :audit_associates, as: :associated, class_name: Audited::AuditAssociate.name
|
|
106
|
+
has_many :associated_audits, through: :audit_associates, source: :audit, class_name: Audited.audit_class.name
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def update_audited_options(new_options)
|
|
110
|
+
previous_audit_options = self.audited_options
|
|
111
|
+
set_audited_options(new_options)
|
|
112
|
+
self.reset_audited_columns
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def set_audited_options(options)
|
|
116
|
+
self.audited_options = options
|
|
117
|
+
normalize_audited_options
|
|
118
|
+
self.audit_associated_with = audited_options[:associated_with]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module AuditedInstanceMethods
|
|
123
|
+
REDACTED = "[REDACTED]"
|
|
124
|
+
|
|
125
|
+
# Temporarily turns off auditing while saving.
|
|
126
|
+
def save_without_auditing
|
|
127
|
+
without_auditing { save }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Executes the block with the auditing callbacks disabled.
|
|
131
|
+
#
|
|
132
|
+
# @foo.without_auditing do
|
|
133
|
+
# @foo.save
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
136
|
+
def without_auditing(&block)
|
|
137
|
+
self.class.without_auditing(&block)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Temporarily turns on auditing while saving.
|
|
141
|
+
def save_with_auditing
|
|
142
|
+
with_auditing { save }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Executes the block with the auditing callbacks enabled.
|
|
146
|
+
#
|
|
147
|
+
# @foo.with_auditing do
|
|
148
|
+
# @foo.save
|
|
149
|
+
# end
|
|
150
|
+
#
|
|
151
|
+
def with_auditing(&block)
|
|
152
|
+
self.class.with_auditing(&block)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Gets an array of the revisions available
|
|
156
|
+
#
|
|
157
|
+
# user.revisions.each do |revision|
|
|
158
|
+
# user.name
|
|
159
|
+
# user.version
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
def revisions(from_version = 1)
|
|
163
|
+
return [] unless audits.from_version(from_version).exists?
|
|
164
|
+
|
|
165
|
+
all_audits = audits.select([:audited_changes, :version, :action]).to_a
|
|
166
|
+
targeted_audits = all_audits.select { |audit| audit.version >= from_version }
|
|
167
|
+
|
|
168
|
+
previous_attributes = reconstruct_attributes(all_audits - targeted_audits)
|
|
169
|
+
|
|
170
|
+
targeted_audits.map do |audit|
|
|
171
|
+
previous_attributes.merge!(audit.new_attributes)
|
|
172
|
+
revision_with(previous_attributes.merge!(version: audit.version))
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Get a specific revision specified by the version number, or +:previous+
|
|
177
|
+
# Returns nil for versions greater than revisions count
|
|
178
|
+
def revision(version)
|
|
179
|
+
if version == :previous || audits.last.version >= version
|
|
180
|
+
revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Find the oldest revision recorded prior to the date/time provided.
|
|
185
|
+
def revision_at(date_or_time)
|
|
186
|
+
audits = self.audits.up_until(date_or_time)
|
|
187
|
+
revision_with Audited.audit_class.reconstruct_attributes(audits) unless audits.empty?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# List of attributes that are audited.
|
|
191
|
+
def audited_attributes
|
|
192
|
+
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
|
193
|
+
audited_attributes = redact_values(audited_attributes)
|
|
194
|
+
audited_attributes = filter_encrypted_attrs(audited_attributes)
|
|
195
|
+
normalize_enum_changes(audited_attributes)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns a list combined of record audits and associated audits.
|
|
199
|
+
def own_and_associated_audits
|
|
200
|
+
Audited.audit_class.unscoped
|
|
201
|
+
.includes(:audit_associates)
|
|
202
|
+
.where(auditable: self)
|
|
203
|
+
.or(Audited.audit_class.unscoped.includes(:audit_associates).where(audit_associates: { associated: self }))
|
|
204
|
+
.order(created_at: :desc)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Combine multiple audits into one.
|
|
208
|
+
def combine_audits(audits_to_combine)
|
|
209
|
+
combine_target = audits_to_combine.last
|
|
210
|
+
combine_target.audited_changes = audits_to_combine.pluck(:audited_changes).reduce(&:merge)
|
|
211
|
+
combine_target.comment = "#{combine_target.comment}\nThis audit is the result of multiple audits being combined."
|
|
212
|
+
|
|
213
|
+
transaction do
|
|
214
|
+
begin
|
|
215
|
+
combine_target.save!
|
|
216
|
+
audits_to_combine.unscope(:limit).where("version < ?", combine_target.version).delete_all
|
|
217
|
+
rescue ActiveRecord::Deadlocked
|
|
218
|
+
# Ignore Deadlocks, if the same record is getting its old audits combined more than once at the same time then
|
|
219
|
+
# both combining operations will be the same. Ignoring this error allows one of the combines to go through successfully.
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
protected
|
|
225
|
+
|
|
226
|
+
def revision_with(attributes)
|
|
227
|
+
dup.tap do |revision|
|
|
228
|
+
revision.id = id
|
|
229
|
+
revision.send :instance_variable_set, "@new_record", destroyed?
|
|
230
|
+
revision.send :instance_variable_set, "@persisted", !destroyed?
|
|
231
|
+
revision.send :instance_variable_set, "@readonly", false
|
|
232
|
+
revision.send :instance_variable_set, "@destroyed", false
|
|
233
|
+
revision.send :instance_variable_set, "@_destroyed", false
|
|
234
|
+
revision.send :instance_variable_set, "@marked_for_destruction", false
|
|
235
|
+
Audited.audit_class.assign_revision_attributes(revision, attributes)
|
|
236
|
+
|
|
237
|
+
# Remove any association proxies so that they will be recreated
|
|
238
|
+
# and reference the correct object for this revision. The only way
|
|
239
|
+
# to determine if an instance variable is a proxy object is to
|
|
240
|
+
# see if it responds to certain methods, as it forwards almost
|
|
241
|
+
# everything to its target.
|
|
242
|
+
revision.instance_variables.each do |ivar|
|
|
243
|
+
proxy = revision.instance_variable_get ivar
|
|
244
|
+
if !proxy.nil? && proxy.respond_to?(:proxy_respond_to?)
|
|
245
|
+
revision.instance_variable_set ivar, nil
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private
|
|
252
|
+
|
|
253
|
+
def audited_changes(for_touch: false, exclude_readonly_attrs: false)
|
|
254
|
+
all_changes = if for_touch
|
|
255
|
+
previous_changes
|
|
256
|
+
elsif respond_to?(:changes_to_save)
|
|
257
|
+
changes_to_save
|
|
258
|
+
else
|
|
259
|
+
changes
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
all_changes = all_changes.except(*self.class.readonly_attributes.to_a) if exclude_readonly_attrs
|
|
263
|
+
|
|
264
|
+
filtered_changes = \
|
|
265
|
+
if audited_options[:only].present?
|
|
266
|
+
all_changes.slice(*self.class.audited_columns)
|
|
267
|
+
else
|
|
268
|
+
all_changes.except(*self.class.non_audited_columns)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
filtered_changes = normalize_enum_changes(filtered_changes)
|
|
272
|
+
|
|
273
|
+
if for_touch && (last_audit = audits.last&.audited_changes)
|
|
274
|
+
filtered_changes.reject! do |k, v|
|
|
275
|
+
last_audit[k].to_json == v.to_json ||
|
|
276
|
+
last_audit[k].to_json == v[1].to_json
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
filtered_changes = redact_values(filtered_changes)
|
|
281
|
+
filtered_changes = filter_encrypted_attrs(filtered_changes)
|
|
282
|
+
filtered_changes.to_hash
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def normalize_enum_changes(changes)
|
|
286
|
+
return changes if Audited.store_synthesized_enums
|
|
287
|
+
|
|
288
|
+
self.class.defined_enums.each do |name, values|
|
|
289
|
+
if changes.has_key?(name)
|
|
290
|
+
changes[name] = \
|
|
291
|
+
if changes[name].is_a?(Array)
|
|
292
|
+
changes[name].map { |v| values[v] }
|
|
293
|
+
elsif rails_below?("5.0")
|
|
294
|
+
changes[name]
|
|
295
|
+
else
|
|
296
|
+
values[changes[name]]
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
changes
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def redact_values(filtered_changes)
|
|
304
|
+
filter_attr_values(
|
|
305
|
+
audited_changes: filtered_changes,
|
|
306
|
+
attrs: Array(audited_options[:redacted]).map(&:to_s),
|
|
307
|
+
placeholder: audited_options[:redaction_value] || REDACTED
|
|
308
|
+
)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def filter_encrypted_attrs(filtered_changes)
|
|
312
|
+
filter_attr_values(
|
|
313
|
+
audited_changes: filtered_changes,
|
|
314
|
+
attrs: respond_to?(:encrypted_attributes) ? Array(encrypted_attributes).map(&:to_s) : []
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Replace values for given attrs to a placeholder and return modified hash
|
|
319
|
+
#
|
|
320
|
+
# @param audited_changes [Hash] Hash of changes to be saved to audited version record
|
|
321
|
+
# @param attrs [Array<String>] Array of attrs, values of which will be replaced to placeholder value
|
|
322
|
+
# @param placeholder [String] Placeholder to replace original attr values
|
|
323
|
+
def filter_attr_values(audited_changes: {}, attrs: [], placeholder: "[FILTERED]")
|
|
324
|
+
attrs.each do |attr|
|
|
325
|
+
next unless audited_changes.key?(attr)
|
|
326
|
+
|
|
327
|
+
changes = audited_changes[attr]
|
|
328
|
+
values = changes.is_a?(Array) ? changes.map { placeholder } : placeholder
|
|
329
|
+
|
|
330
|
+
audited_changes[attr] = values
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
audited_changes
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def rails_below?(rails_version)
|
|
337
|
+
::ActiveRecord.version < Gem::Version.new(rails_version)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def audits_to(version = nil)
|
|
341
|
+
if version == :previous
|
|
342
|
+
version = if audit_version
|
|
343
|
+
audit_version - 1
|
|
344
|
+
else
|
|
345
|
+
previous = audits.descending.offset(1).first
|
|
346
|
+
previous ? previous.version : 1
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
audits.to_version(version)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def audit_create
|
|
353
|
+
write_audit(action: "create", audited_changes: audited_attributes,
|
|
354
|
+
comment: audit_comment)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def audit_update
|
|
358
|
+
unless (changes = audited_changes(exclude_readonly_attrs: true)).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
|
359
|
+
write_audit(action: "update", audited_changes: changes,
|
|
360
|
+
comment: audit_comment)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def audit_touch
|
|
365
|
+
unless (changes = audited_changes(for_touch: true, exclude_readonly_attrs: true)).empty?
|
|
366
|
+
write_audit(action: "update", audited_changes: changes,
|
|
367
|
+
comment: audit_comment)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def audit_destroy
|
|
372
|
+
unless new_record?
|
|
373
|
+
write_audit(action: "destroy", audited_changes: audited_attributes,
|
|
374
|
+
comment: audit_comment)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def write_audit(attrs)
|
|
379
|
+
self.audit_comment = nil
|
|
380
|
+
|
|
381
|
+
if auditing_enabled
|
|
382
|
+
run_callbacks(:audit) do
|
|
383
|
+
audit = audits.create(attrs)
|
|
384
|
+
audit.audit_associates << collect_audit_associated_with unless audit_associated_with.nil?
|
|
385
|
+
combine_audits_if_needed if attrs[:action] != 'create'
|
|
386
|
+
audit
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def presence_of_audit_comment
|
|
392
|
+
if comment_required_state?
|
|
393
|
+
errors.add(:audit_comment, :blank) unless audit_comment.present?
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def comment_required_state?
|
|
398
|
+
auditing_enabled &&
|
|
399
|
+
audited_changes.present? &&
|
|
400
|
+
((audited_options[:on].include?(:create) && new_record?) ||
|
|
401
|
+
(audited_options[:on].include?(:update) && persisted? && changed?))
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def combine_audits_if_needed
|
|
405
|
+
max_audits = evaluate_max_audits
|
|
406
|
+
|
|
407
|
+
if max_audits && (extra_count = audits.count - max_audits) > 0
|
|
408
|
+
audits_to_combine = audits.limit(extra_count + 1)
|
|
409
|
+
combine_audits(audits_to_combine)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def evaluate_max_audits
|
|
414
|
+
max_audits = case (option = audited_options[:max_audits])
|
|
415
|
+
when Proc then option.call
|
|
416
|
+
when Symbol then send(option)
|
|
417
|
+
else
|
|
418
|
+
option
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
Integer(max_audits).abs if max_audits
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def require_comment
|
|
425
|
+
if auditing_enabled && audit_comment.blank?
|
|
426
|
+
errors.add(:audit_comment, :blank)
|
|
427
|
+
throw(:abort)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
CALLBACKS.each do |attr_name|
|
|
432
|
+
alias_method "#{attr_name}_callback".to_sym, attr_name
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def auditing_enabled
|
|
436
|
+
run_conditional_check(audited_options[:if]) &&
|
|
437
|
+
run_conditional_check(audited_options[:unless], matching: false) &&
|
|
438
|
+
self.class.auditing_enabled
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def run_conditional_check(condition, matching: true)
|
|
442
|
+
return true if condition.blank?
|
|
443
|
+
return condition.call(self) == matching if condition.respond_to?(:call)
|
|
444
|
+
return send(condition) == matching if respond_to?(condition.to_sym, true)
|
|
445
|
+
|
|
446
|
+
true
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def reconstruct_attributes(audits)
|
|
450
|
+
attributes = {}
|
|
451
|
+
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
|
452
|
+
attributes
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def collect_audit_associated_with
|
|
456
|
+
Array(audit_associated_with).map do |associated|
|
|
457
|
+
Audited::AuditAssociate.new(associated: send(associated))
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
module AuditedClassMethods
|
|
463
|
+
# Returns an array of columns that are audited. See non_audited_columns
|
|
464
|
+
def audited_columns
|
|
465
|
+
@audited_columns ||= column_names - non_audited_columns
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# We have to calculate this here since column_names may not be available when `audited` is called
|
|
469
|
+
def non_audited_columns
|
|
470
|
+
@non_audited_columns ||= calculate_non_audited_columns
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def non_audited_columns=(columns)
|
|
474
|
+
@audited_columns = nil # reset cached audited columns on assignment
|
|
475
|
+
@non_audited_columns = columns.map(&:to_s)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Executes the block with auditing disabled.
|
|
479
|
+
#
|
|
480
|
+
# Foo.without_auditing do
|
|
481
|
+
# @foo.save
|
|
482
|
+
# end
|
|
483
|
+
#
|
|
484
|
+
def without_auditing
|
|
485
|
+
auditing_was_enabled = class_auditing_enabled
|
|
486
|
+
disable_auditing
|
|
487
|
+
yield
|
|
488
|
+
ensure
|
|
489
|
+
enable_auditing if auditing_was_enabled
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Executes the block with auditing enabled.
|
|
493
|
+
#
|
|
494
|
+
# Foo.with_auditing do
|
|
495
|
+
# @foo.save
|
|
496
|
+
# end
|
|
497
|
+
#
|
|
498
|
+
def with_auditing
|
|
499
|
+
auditing_was_enabled = class_auditing_enabled
|
|
500
|
+
enable_auditing
|
|
501
|
+
yield
|
|
502
|
+
ensure
|
|
503
|
+
disable_auditing unless auditing_was_enabled
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def disable_auditing
|
|
507
|
+
self.auditing_enabled = false
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def enable_auditing
|
|
511
|
+
self.auditing_enabled = true
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# All audit operations during the block are recorded as being
|
|
515
|
+
# made by +user+. This is not model specific, the method is a
|
|
516
|
+
# convenience wrapper around
|
|
517
|
+
# @see Audit#as_user.
|
|
518
|
+
def audit_as(user, &block)
|
|
519
|
+
Audited.audit_class.as_user(user, &block)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def auditing_enabled
|
|
523
|
+
class_auditing_enabled && Audited.auditing_enabled
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def auditing_enabled=(val)
|
|
527
|
+
Audited.store["#{table_name}_auditing_enabled"] = val
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def default_ignored_attributes
|
|
531
|
+
[primary_key, inheritance_column] | Audited.ignored_attributes
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
protected
|
|
535
|
+
|
|
536
|
+
def normalize_audited_options
|
|
537
|
+
audited_options[:on] = Array.wrap(audited_options[:on])
|
|
538
|
+
audited_options[:on] = ([:create, :update, :touch, :destroy] - Audited.ignored_default_callbacks) if audited_options[:on].empty?
|
|
539
|
+
audited_options[:only] = Array.wrap(audited_options[:only]).map(&:to_s)
|
|
540
|
+
audited_options[:except] = Array.wrap(audited_options[:except]).map(&:to_s)
|
|
541
|
+
audited_options[:max_audits] ||= Audited.max_audits
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def calculate_non_audited_columns
|
|
545
|
+
if audited_options[:only].present?
|
|
546
|
+
(column_names | default_ignored_attributes) - audited_options[:only]
|
|
547
|
+
elsif audited_options[:except].present?
|
|
548
|
+
default_ignored_attributes | audited_options[:except]
|
|
549
|
+
else
|
|
550
|
+
default_ignored_attributes
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def class_auditing_enabled
|
|
555
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def reset_audited_columns
|
|
559
|
+
@audited_columns = nil
|
|
560
|
+
@non_audited_columns = nil
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Audited
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer "audited.sweeper" do
|
|
6
|
+
ActiveSupport.on_load(:action_controller) do
|
|
7
|
+
if defined?(ActionController::Base)
|
|
8
|
+
ActionController::Base.around_action Audited::Sweeper.new
|
|
9
|
+
end
|
|
10
|
+
if defined?(ActionController::API)
|
|
11
|
+
ActionController::API.around_action Audited::Sweeper.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|