audited 4.9.0 → 5.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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