audited 4.9.0 → 5.4.3
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 +4 -4
- data/.github/workflows/buildlight.yml +15 -0
- data/.github/workflows/ci.yml +145 -0
- data/.github/workflows/publish_gem.yml +28 -0
- data/.standard.yml +5 -0
- data/Appraisals +35 -16
- data/CHANGELOG.md +162 -1
- data/Gemfile +1 -1
- data/README.md +73 -18
- data/Rakefile +5 -7
- data/gemfiles/rails50.gemfile +2 -0
- data/gemfiles/rails51.gemfile +2 -0
- data/gemfiles/rails52.gemfile +3 -1
- data/gemfiles/rails60.gemfile +1 -1
- data/gemfiles/rails61.gemfile +10 -0
- data/gemfiles/rails70.gemfile +10 -0
- data/gemfiles/rails71.gemfile +10 -0
- data/lib/audited/audit.rb +41 -29
- data/lib/audited/auditor.rb +134 -56
- data/lib/audited/railtie.rb +16 -0
- data/lib/audited/rspec_matchers.rb +5 -3
- data/lib/audited/sweeper.rb +3 -10
- data/lib/audited/version.rb +3 -1
- data/lib/audited-rspec.rb +3 -1
- data/lib/audited.rb +31 -9
- data/lib/generators/audited/install_generator.rb +9 -7
- data/lib/generators/audited/migration.rb +12 -2
- data/lib/generators/audited/migration_helper.rb +3 -1
- data/lib/generators/audited/templates/add_association_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_version_to_auditable_index.rb +2 -0
- data/lib/generators/audited/templates/install.rb +2 -0
- data/lib/generators/audited/templates/rename_association_to_associated.rb +2 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +2 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +2 -0
- data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +2 -0
- data/lib/generators/audited/upgrade_generator.rb +16 -14
- data/spec/audited/audit_spec.rb +70 -48
- data/spec/audited/auditor_spec.rb +477 -246
- data/spec/audited/sweeper_spec.rb +19 -18
- data/spec/audited_spec.rb +14 -0
- data/spec/audited_spec_helpers.rb +11 -7
- data/spec/rails_app/app/assets/config/manifest.js +2 -0
- data/spec/rails_app/config/application.rb +32 -3
- data/spec/rails_app/config/database.yml +3 -2
- data/spec/rails_app/config/environment.rb +1 -1
- data/spec/rails_app/config/environments/test.rb +10 -5
- data/spec/rails_app/config/initializers/secret_token.rb +2 -2
- data/spec/spec_helper.rb +14 -14
- data/spec/support/active_record/models.rb +62 -13
- data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +1 -2
- data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +1 -2
- data/spec/support/active_record/schema.rb +26 -19
- data/test/db/version_1.rb +2 -2
- data/test/db/version_2.rb +2 -2
- data/test/db/version_3.rb +2 -3
- data/test/db/version_4.rb +2 -3
- data/test/db/version_5.rb +0 -1
- data/test/db/version_6.rb +1 -1
- data/test/install_generator_test.rb +18 -19
- data/test/test_helper.rb +5 -5
- data/test/upgrade_generator_test.rb +13 -18
- metadata +49 -31
- data/.rubocop.yml +0 -25
- data/.travis.yml +0 -58
- data/gemfiles/rails42.gemfile +0 -11
- data/spec/rails_app/app/controllers/application_controller.rb +0 -2
- data/spec/rails_app/config/environments/development.rb +0 -21
- data/spec/rails_app/config/environments/production.rb +0 -35
data/gemfiles/rails51.gemfile
CHANGED
data/gemfiles/rails52.gemfile
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "rails", ">= 5.2.
|
5
|
+
gem "rails", ">= 5.2.8.1", "< 5.3"
|
6
6
|
gem "mysql2", ">= 0.4.4", "< 0.6.0"
|
7
7
|
gem "pg", ">= 0.18", "< 2.0"
|
8
8
|
gem "sqlite3", "~> 1.3.6"
|
9
|
+
gem "psych", "~> 3.1"
|
10
|
+
gem "loofah", "2.20.0"
|
9
11
|
|
10
12
|
gemspec name: "audited", path: "../"
|
data/gemfiles/rails60.gemfile
CHANGED
data/lib/audited/audit.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
2
4
|
|
3
5
|
module Audited
|
4
6
|
# Audit saves the changes to ActiveRecord models. It has the following attributes:
|
@@ -16,7 +18,7 @@ module Audited
|
|
16
18
|
class YAMLIfTextColumnType
|
17
19
|
class << self
|
18
20
|
def load(obj)
|
19
|
-
if
|
21
|
+
if text_column?
|
20
22
|
ActiveRecord::Coders::YAMLColumn.new(Object).load(obj)
|
21
23
|
else
|
22
24
|
obj
|
@@ -24,18 +26,22 @@ module Audited
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def dump(obj)
|
27
|
-
if
|
29
|
+
if text_column?
|
28
30
|
ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj)
|
29
31
|
else
|
30
32
|
obj
|
31
33
|
end
|
32
34
|
end
|
35
|
+
|
36
|
+
def text_column?
|
37
|
+
Audited.audit_class.columns_hash["audited_changes"].type.to_s == "text"
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
36
42
|
class Audit < ::ActiveRecord::Base
|
37
|
-
belongs_to :auditable,
|
38
|
-
belongs_to :user,
|
43
|
+
belongs_to :auditable, polymorphic: true
|
44
|
+
belongs_to :user, polymorphic: true
|
39
45
|
belongs_to :associated, polymorphic: true
|
40
46
|
|
41
47
|
before_create :set_version_number, :set_audit_user, :set_request_uuid, :set_remote_address
|
@@ -43,18 +49,22 @@ module Audited
|
|
43
49
|
cattr_accessor :audited_class_names
|
44
50
|
self.audited_class_names = Set.new
|
45
51
|
|
46
|
-
|
52
|
+
if Rails.gem_version >= Gem::Version.new("7.1")
|
53
|
+
serialize :audited_changes, coder: YAMLIfTextColumnType
|
54
|
+
else
|
55
|
+
serialize :audited_changes, YAMLIfTextColumnType
|
56
|
+
end
|
47
57
|
|
48
|
-
scope :ascending,
|
49
|
-
scope :descending,
|
50
|
-
scope :creates,
|
51
|
-
scope :updates,
|
52
|
-
scope :destroys,
|
58
|
+
scope :ascending, -> { reorder(version: :asc) }
|
59
|
+
scope :descending, -> { reorder(version: :desc) }
|
60
|
+
scope :creates, -> { where(action: "create") }
|
61
|
+
scope :updates, -> { where(action: "update") }
|
62
|
+
scope :destroys, -> { where(action: "destroy") }
|
53
63
|
|
54
|
-
scope :up_until,
|
55
|
-
scope :from_version,
|
56
|
-
scope :to_version,
|
57
|
-
scope :auditable_finder, ->(auditable_id, auditable_type){ where(auditable_id: auditable_id, auditable_type: auditable_type)}
|
64
|
+
scope :up_until, ->(date_or_time) { where("created_at <= ?", date_or_time) }
|
65
|
+
scope :from_version, ->(version) { where("version >= ?", version) }
|
66
|
+
scope :to_version, ->(version) { where("version <= ?", version) }
|
67
|
+
scope :auditable_finder, ->(auditable_id, auditable_type) { where(auditable_id: auditable_id, auditable_type: auditable_type) }
|
58
68
|
# Return all audits older than the current one.
|
59
69
|
def ancestors
|
60
70
|
self.class.ascending.auditable_finder(auditable_id, auditable_type).to_version(version)
|
@@ -71,31 +81,28 @@ module Audited
|
|
71
81
|
|
72
82
|
# Returns a hash of the changed attributes with the new values
|
73
83
|
def new_attributes
|
74
|
-
(audited_changes || {}).
|
75
|
-
attrs[attr] =
|
76
|
-
attrs
|
84
|
+
(audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|
|
85
|
+
attrs[attr] = (action == "update") ? values.last : values
|
77
86
|
end
|
78
87
|
end
|
79
88
|
|
80
89
|
# Returns a hash of the changed attributes with the old values
|
81
90
|
def old_attributes
|
82
|
-
(audited_changes || {}).
|
83
|
-
attrs[attr] =
|
84
|
-
|
85
|
-
attrs
|
91
|
+
(audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|
|
92
|
+
attrs[attr] = (action == "update") ? values.first : values
|
86
93
|
end
|
87
94
|
end
|
88
95
|
|
89
96
|
# Allows user to undo changes
|
90
97
|
def undo
|
91
98
|
case action
|
92
|
-
when
|
99
|
+
when "create"
|
93
100
|
# destroys a newly created record
|
94
101
|
auditable.destroy!
|
95
|
-
when
|
102
|
+
when "destroy"
|
96
103
|
# creates a new record with the destroyed record attributes
|
97
104
|
auditable_type.constantize.create!(audited_changes)
|
98
|
-
when
|
105
|
+
when "update"
|
99
106
|
# changes back attributes
|
100
107
|
auditable.update!(audited_changes.transform_values(&:first))
|
101
108
|
else
|
@@ -131,7 +138,7 @@ module Audited
|
|
131
138
|
# by +user+. This method is hopefully threadsafe, making it ideal
|
132
139
|
# for background operations that require audit information.
|
133
140
|
def self.as_user(user)
|
134
|
-
last_audited_user = ::Audited.store[:audited_user]
|
141
|
+
last_audited_user = ::Audited.store[:audited_user]
|
135
142
|
::Audited.store[:audited_user] = user
|
136
143
|
yield
|
137
144
|
ensure
|
@@ -143,7 +150,7 @@ module Audited
|
|
143
150
|
audits.each_with_object({}) do |audit, all|
|
144
151
|
all.merge!(audit.new_attributes)
|
145
152
|
all[:audit_version] = audit.version
|
146
|
-
|
153
|
+
end
|
147
154
|
end
|
148
155
|
|
149
156
|
# @private
|
@@ -168,8 +175,13 @@ module Audited
|
|
168
175
|
private
|
169
176
|
|
170
177
|
def set_version_number
|
171
|
-
|
172
|
-
|
178
|
+
if action == "create"
|
179
|
+
self.version = 1
|
180
|
+
else
|
181
|
+
collection = (ActiveRecord::VERSION::MAJOR >= 6) ? self.class.unscoped : self.class
|
182
|
+
max = collection.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
|
183
|
+
self.version = max + 1
|
184
|
+
end
|
173
185
|
end
|
174
186
|
|
175
187
|
def set_audit_user
|
data/lib/audited/auditor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Audited
|
2
4
|
# Specify this act if you want changes to your model to be saved in an
|
3
5
|
# audit table. This assumes there is an audits table ready.
|
@@ -11,7 +13,7 @@ module Audited
|
|
11
13
|
#
|
12
14
|
# See <tt>Audited::Auditor::ClassMethods#audited</tt>
|
13
15
|
# for configuration options
|
14
|
-
module Auditor
|
16
|
+
module Auditor # :nodoc:
|
15
17
|
extend ActiveSupport::Concern
|
16
18
|
|
17
19
|
CALLBACKS = [:audit_create, :audit_update, :audit_destroy]
|
@@ -34,6 +36,16 @@ module Audited
|
|
34
36
|
# * +require_comment+ - Ensures that audit_comment is supplied before
|
35
37
|
# any create, update or destroy operation.
|
36
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
|
37
49
|
#
|
38
50
|
# * +if+ - Only audit the model when the given function returns true
|
39
51
|
# * +unless+ - Only audit the model when the given function returns false
|
@@ -54,7 +66,7 @@ module Audited
|
|
54
66
|
include Audited::Auditor::AuditedInstanceMethods
|
55
67
|
|
56
68
|
class_attribute :audit_associated_with, instance_writer: false
|
57
|
-
class_attribute :audited_options,
|
69
|
+
class_attribute :audited_options, instance_writer: false
|
58
70
|
attr_accessor :audit_version, :audit_comment
|
59
71
|
|
60
72
|
self.audited_options = options
|
@@ -70,8 +82,9 @@ module Audited
|
|
70
82
|
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name, inverse_of: :auditable
|
71
83
|
Audited.audit_class.audited_class_names << to_s
|
72
84
|
|
73
|
-
after_create :audit_create
|
74
|
-
before_update :audit_update
|
85
|
+
after_create :audit_create if audited_options[:on].include?(:create)
|
86
|
+
before_update :audit_update if audited_options[:on].include?(:update)
|
87
|
+
after_touch :audit_touch if audited_options[:on].include?(:touch) && ::ActiveRecord::VERSION::MAJOR >= 6
|
75
88
|
before_destroy :audit_destroy if audited_options[:on].include?(:destroy)
|
76
89
|
|
77
90
|
# Define and set after_audit and around_audit callbacks. This might be useful if you want
|
@@ -90,15 +103,7 @@ module Audited
|
|
90
103
|
end
|
91
104
|
|
92
105
|
module AuditedInstanceMethods
|
93
|
-
|
94
|
-
def method_missing(method_name, *args, &block)
|
95
|
-
if method_name == :version
|
96
|
-
ActiveSupport::Deprecation.warn("`version` attribute has been changed to `audit_version`. This attribute will be removed.")
|
97
|
-
audit_version
|
98
|
-
else
|
99
|
-
super
|
100
|
-
end
|
101
|
-
end
|
106
|
+
REDACTED = "[REDACTED]"
|
102
107
|
|
103
108
|
# Temporarily turns off auditing while saving.
|
104
109
|
def save_without_auditing
|
@@ -140,7 +145,7 @@ module Audited
|
|
140
145
|
def revisions(from_version = 1)
|
141
146
|
return [] unless audits.from_version(from_version).exists?
|
142
147
|
|
143
|
-
all_audits = audits.select([:audited_changes, :version]).to_a
|
148
|
+
all_audits = audits.select([:audited_changes, :version, :action]).to_a
|
144
149
|
targeted_audits = all_audits.select { |audit| audit.version >= from_version }
|
145
150
|
|
146
151
|
previous_attributes = reconstruct_attributes(all_audits - targeted_audits)
|
@@ -154,7 +159,7 @@ module Audited
|
|
154
159
|
# Get a specific revision specified by the version number, or +:previous+
|
155
160
|
# Returns nil for versions greater than revisions count
|
156
161
|
def revision(version)
|
157
|
-
if version == :previous ||
|
162
|
+
if version == :previous || audits.last.version >= version
|
158
163
|
revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
|
159
164
|
end
|
160
165
|
end
|
@@ -168,15 +173,16 @@ module Audited
|
|
168
173
|
# List of attributes that are audited.
|
169
174
|
def audited_attributes
|
170
175
|
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
176
|
+
audited_attributes = redact_values(audited_attributes)
|
177
|
+
audited_attributes = filter_encrypted_attrs(audited_attributes)
|
171
178
|
normalize_enum_changes(audited_attributes)
|
172
179
|
end
|
173
180
|
|
174
181
|
# Returns a list combined of record audits and associated audits.
|
175
182
|
def own_and_associated_audits
|
176
|
-
Audited.audit_class.unscoped
|
177
|
-
|
178
|
-
|
179
|
-
.order(created_at: :desc)
|
183
|
+
Audited.audit_class.unscoped.where(auditable: self)
|
184
|
+
.or(Audited.audit_class.unscoped.where(associated: self))
|
185
|
+
.order(created_at: :desc)
|
180
186
|
end
|
181
187
|
|
182
188
|
# Combine multiple audits into one.
|
@@ -186,8 +192,13 @@ module Audited
|
|
186
192
|
combine_target.comment = "#{combine_target.comment}\nThis audit is the result of multiple audits being combined."
|
187
193
|
|
188
194
|
transaction do
|
189
|
-
|
190
|
-
|
195
|
+
begin
|
196
|
+
combine_target.save!
|
197
|
+
audits_to_combine.unscope(:limit).where("version < ?", combine_target.version).delete_all
|
198
|
+
rescue ActiveRecord::Deadlocked
|
199
|
+
# Ignore Deadlocks, if the same record is getting its old audits combined more than once at the same time then
|
200
|
+
# both combining operations will be the same. Ignoring this error allows one of the combines to go through successfully.
|
201
|
+
end
|
191
202
|
end
|
192
203
|
end
|
193
204
|
|
@@ -196,12 +207,12 @@ module Audited
|
|
196
207
|
def revision_with(attributes)
|
197
208
|
dup.tap do |revision|
|
198
209
|
revision.id = id
|
199
|
-
revision.send :instance_variable_set,
|
200
|
-
revision.send :instance_variable_set,
|
201
|
-
revision.send :instance_variable_set,
|
202
|
-
revision.send :instance_variable_set,
|
203
|
-
revision.send :instance_variable_set,
|
204
|
-
revision.send :instance_variable_set,
|
210
|
+
revision.send :instance_variable_set, "@new_record", destroyed?
|
211
|
+
revision.send :instance_variable_set, "@persisted", !destroyed?
|
212
|
+
revision.send :instance_variable_set, "@readonly", false
|
213
|
+
revision.send :instance_variable_set, "@destroyed", false
|
214
|
+
revision.send :instance_variable_set, "@_destroyed", false
|
215
|
+
revision.send :instance_variable_set, "@marked_for_destruction", false
|
205
216
|
Audited.audit_class.assign_revision_attributes(revision, attributes)
|
206
217
|
|
207
218
|
# Remove any association proxies so that they will be recreated
|
@@ -220,8 +231,17 @@ module Audited
|
|
220
231
|
|
221
232
|
private
|
222
233
|
|
223
|
-
def audited_changes
|
224
|
-
all_changes =
|
234
|
+
def audited_changes(for_touch: false, exclude_readonly_attrs: false)
|
235
|
+
all_changes = if for_touch
|
236
|
+
previous_changes
|
237
|
+
elsif respond_to?(:changes_to_save)
|
238
|
+
changes_to_save
|
239
|
+
else
|
240
|
+
changes
|
241
|
+
end
|
242
|
+
|
243
|
+
all_changes = all_changes.except(*self.class.readonly_attributes.to_a) if exclude_readonly_attrs
|
244
|
+
|
225
245
|
filtered_changes = \
|
226
246
|
if audited_options[:only].present?
|
227
247
|
all_changes.slice(*self.class.audited_columns)
|
@@ -229,17 +249,28 @@ module Audited
|
|
229
249
|
all_changes.except(*self.class.non_audited_columns)
|
230
250
|
end
|
231
251
|
|
252
|
+
if for_touch && (last_audit = audits.last&.audited_changes)
|
253
|
+
filtered_changes.reject! do |k, v|
|
254
|
+
last_audit[k].to_json == v.to_json ||
|
255
|
+
last_audit[k].to_json == v[1].to_json
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
filtered_changes = redact_values(filtered_changes)
|
260
|
+
filtered_changes = filter_encrypted_attrs(filtered_changes)
|
232
261
|
filtered_changes = normalize_enum_changes(filtered_changes)
|
233
262
|
filtered_changes.to_hash
|
234
263
|
end
|
235
264
|
|
236
265
|
def normalize_enum_changes(changes)
|
266
|
+
return changes if Audited.store_synthesized_enums
|
267
|
+
|
237
268
|
self.class.defined_enums.each do |name, values|
|
238
269
|
if changes.has_key?(name)
|
239
270
|
changes[name] = \
|
240
271
|
if changes[name].is_a?(Array)
|
241
272
|
changes[name].map { |v| values[v] }
|
242
|
-
elsif rails_below?(
|
273
|
+
elsif rails_below?("5.0")
|
243
274
|
changes[name]
|
244
275
|
else
|
245
276
|
values[changes[name]]
|
@@ -249,47 +280,90 @@ module Audited
|
|
249
280
|
changes
|
250
281
|
end
|
251
282
|
|
283
|
+
def redact_values(filtered_changes)
|
284
|
+
filter_attr_values(
|
285
|
+
audited_changes: filtered_changes,
|
286
|
+
attrs: Array(audited_options[:redacted]).map(&:to_s),
|
287
|
+
placeholder: audited_options[:redaction_value] || REDACTED
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
def filter_encrypted_attrs(filtered_changes)
|
292
|
+
filter_attr_values(
|
293
|
+
audited_changes: filtered_changes,
|
294
|
+
attrs: respond_to?(:encrypted_attributes) ? Array(encrypted_attributes).map(&:to_s) : []
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
# Replace values for given attrs to a placeholder and return modified hash
|
299
|
+
#
|
300
|
+
# @param audited_changes [Hash] Hash of changes to be saved to audited version record
|
301
|
+
# @param attrs [Array<String>] Array of attrs, values of which will be replaced to placeholder value
|
302
|
+
# @param placeholder [String] Placeholder to replace original attr values
|
303
|
+
def filter_attr_values(audited_changes: {}, attrs: [], placeholder: "[FILTERED]")
|
304
|
+
attrs.each do |attr|
|
305
|
+
next unless audited_changes.key?(attr)
|
306
|
+
|
307
|
+
changes = audited_changes[attr]
|
308
|
+
values = changes.is_a?(Array) ? changes.map { placeholder } : placeholder
|
309
|
+
|
310
|
+
audited_changes[attr] = values
|
311
|
+
end
|
312
|
+
|
313
|
+
audited_changes
|
314
|
+
end
|
315
|
+
|
252
316
|
def rails_below?(rails_version)
|
253
317
|
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
254
318
|
end
|
255
319
|
|
256
320
|
def audits_to(version = nil)
|
257
321
|
if version == :previous
|
258
|
-
version = if
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
322
|
+
version = if audit_version
|
323
|
+
audit_version - 1
|
324
|
+
else
|
325
|
+
previous = audits.descending.offset(1).first
|
326
|
+
previous ? previous.version : 1
|
327
|
+
end
|
264
328
|
end
|
265
329
|
audits.to_version(version)
|
266
330
|
end
|
267
331
|
|
268
332
|
def audit_create
|
269
|
-
write_audit(action:
|
270
|
-
|
333
|
+
write_audit(action: "create", audited_changes: audited_attributes,
|
334
|
+
comment: audit_comment)
|
271
335
|
end
|
272
336
|
|
273
337
|
def audit_update
|
274
|
-
unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
275
|
-
write_audit(action:
|
276
|
-
|
338
|
+
unless (changes = audited_changes(exclude_readonly_attrs: true)).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
339
|
+
write_audit(action: "update", audited_changes: changes,
|
340
|
+
comment: audit_comment)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def audit_touch
|
345
|
+
unless (changes = audited_changes(for_touch: true, exclude_readonly_attrs: true)).empty?
|
346
|
+
write_audit(action: "update", audited_changes: changes,
|
347
|
+
comment: audit_comment)
|
277
348
|
end
|
278
349
|
end
|
279
350
|
|
280
351
|
def audit_destroy
|
281
|
-
|
282
|
-
|
352
|
+
unless new_record?
|
353
|
+
write_audit(action: "destroy", audited_changes: audited_attributes,
|
354
|
+
comment: audit_comment)
|
355
|
+
end
|
283
356
|
end
|
284
357
|
|
285
358
|
def write_audit(attrs)
|
286
|
-
attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil?
|
287
359
|
self.audit_comment = nil
|
288
360
|
|
289
361
|
if auditing_enabled
|
362
|
+
attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil?
|
363
|
+
|
290
364
|
run_callbacks(:audit) {
|
291
365
|
audit = audits.create(attrs)
|
292
|
-
combine_audits_if_needed if attrs[:action] !=
|
366
|
+
combine_audits_if_needed if attrs[:action] != "create"
|
293
367
|
audit
|
294
368
|
}
|
295
369
|
end
|
@@ -297,14 +371,15 @@ module Audited
|
|
297
371
|
|
298
372
|
def presence_of_audit_comment
|
299
373
|
if comment_required_state?
|
300
|
-
errors.add(:audit_comment,
|
374
|
+
errors.add(:audit_comment, :blank) unless audit_comment.present?
|
301
375
|
end
|
302
376
|
end
|
303
377
|
|
304
378
|
def comment_required_state?
|
305
379
|
auditing_enabled &&
|
306
|
-
|
307
|
-
(audited_options[:on].include?(:
|
380
|
+
audited_changes.present? &&
|
381
|
+
((audited_options[:on].include?(:create) && new_record?) ||
|
382
|
+
(audited_options[:on].include?(:update) && persisted? && changed?))
|
308
383
|
end
|
309
384
|
|
310
385
|
def combine_audits_if_needed
|
@@ -317,8 +392,7 @@ module Audited
|
|
317
392
|
|
318
393
|
def require_comment
|
319
394
|
if auditing_enabled && audit_comment.blank?
|
320
|
-
errors.add(:audit_comment,
|
321
|
-
return false if Rails.version.start_with?('4.')
|
395
|
+
errors.add(:audit_comment, :blank)
|
322
396
|
throw(:abort)
|
323
397
|
end
|
324
398
|
end
|
@@ -328,7 +402,7 @@ module Audited
|
|
328
402
|
end
|
329
403
|
|
330
404
|
def auditing_enabled
|
331
|
-
|
405
|
+
run_conditional_check(audited_options[:if]) &&
|
332
406
|
run_conditional_check(audited_options[:unless], matching: false) &&
|
333
407
|
self.class.auditing_enabled
|
334
408
|
end
|
@@ -346,7 +420,7 @@ module Audited
|
|
346
420
|
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
347
421
|
attributes
|
348
422
|
end
|
349
|
-
end
|
423
|
+
end
|
350
424
|
|
351
425
|
module AuditedClassMethods
|
352
426
|
# Returns an array of columns that are audited. See non_audited_columns
|
@@ -371,7 +445,7 @@ module Audited
|
|
371
445
|
# end
|
372
446
|
#
|
373
447
|
def without_auditing
|
374
|
-
auditing_was_enabled =
|
448
|
+
auditing_was_enabled = class_auditing_enabled
|
375
449
|
disable_auditing
|
376
450
|
yield
|
377
451
|
ensure
|
@@ -385,7 +459,7 @@ module Audited
|
|
385
459
|
# end
|
386
460
|
#
|
387
461
|
def with_auditing
|
388
|
-
auditing_was_enabled =
|
462
|
+
auditing_was_enabled = class_auditing_enabled
|
389
463
|
enable_auditing
|
390
464
|
yield
|
391
465
|
ensure
|
@@ -409,7 +483,7 @@ module Audited
|
|
409
483
|
end
|
410
484
|
|
411
485
|
def auditing_enabled
|
412
|
-
|
486
|
+
class_auditing_enabled && Audited.auditing_enabled
|
413
487
|
end
|
414
488
|
|
415
489
|
def auditing_enabled=(val)
|
@@ -424,7 +498,7 @@ module Audited
|
|
424
498
|
|
425
499
|
def normalize_audited_options
|
426
500
|
audited_options[:on] = Array.wrap(audited_options[:on])
|
427
|
-
audited_options[:on] = [:create, :update, :destroy] if audited_options[:on].empty?
|
501
|
+
audited_options[:on] = ([:create, :update, :touch, :destroy] - Audited.ignored_default_callbacks) if audited_options[:on].empty?
|
428
502
|
audited_options[:only] = Array.wrap(audited_options[:only]).map(&:to_s)
|
429
503
|
audited_options[:except] = Array.wrap(audited_options[:except]).map(&:to_s)
|
430
504
|
max_audits = audited_options[:max_audits] || Audited.max_audits
|
@@ -440,6 +514,10 @@ module Audited
|
|
440
514
|
default_ignored_attributes
|
441
515
|
end
|
442
516
|
end
|
517
|
+
|
518
|
+
def class_auditing_enabled
|
519
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
520
|
+
end
|
443
521
|
end
|
444
522
|
end
|
445
523
|
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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Audited
|
2
4
|
module RspecMatchers
|
3
5
|
# Ensure that the model is audited.
|
@@ -78,9 +80,9 @@ module Audited
|
|
78
80
|
def description
|
79
81
|
description = "audited"
|
80
82
|
description += " associated with #{@options[:associated_with]}" if @options.key?(:associated_with)
|
81
|
-
description += " only => #{@options[:only].join
|
82
|
-
description += " except => #{@options[:except].join(
|
83
|
-
description += " requires audit_comment"
|
83
|
+
description += " only => #{@options[:only].join ", "}" if @options.key?(:only)
|
84
|
+
description += " except => #{@options[:except].join(", ")}" if @options.key?(:except)
|
85
|
+
description += " requires audit_comment" if @options.key?(:comment_required)
|
84
86
|
|
85
87
|
description
|
86
88
|
end
|
data/lib/audited/sweeper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Audited
|
2
4
|
class Sweeper
|
3
5
|
STORED_DATA = {
|
@@ -10,7 +12,7 @@ module Audited
|
|
10
12
|
|
11
13
|
def around(controller)
|
12
14
|
self.controller = controller
|
13
|
-
STORED_DATA.each { |k,m| store[k] = send(m) }
|
15
|
+
STORED_DATA.each { |k, m| store[k] = send(m) }
|
14
16
|
yield
|
15
17
|
ensure
|
16
18
|
self.controller = nil
|
@@ -38,12 +40,3 @@ module Audited
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
41
|
-
|
42
|
-
ActiveSupport.on_load(:action_controller) do
|
43
|
-
if defined?(ActionController::Base)
|
44
|
-
ActionController::Base.around_action Audited::Sweeper.new
|
45
|
-
end
|
46
|
-
if defined?(ActionController::API)
|
47
|
-
ActionController::API.around_action Audited::Sweeper.new
|
48
|
-
end
|
49
|
-
end
|
data/lib/audited/version.rb
CHANGED