audited 4.7.0 → 4.9.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 +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +25 -0
- data/.travis.yml +27 -27
- data/Appraisals +22 -12
- data/CHANGELOG.md +61 -1
- data/README.md +63 -17
- data/gemfiles/rails42.gemfile +3 -0
- data/gemfiles/rails50.gemfile +3 -0
- data/gemfiles/rails51.gemfile +3 -0
- data/gemfiles/rails52.gemfile +4 -2
- data/gemfiles/rails60.gemfile +10 -0
- data/lib/audited/audit.rb +19 -22
- data/lib/audited/auditor.rb +83 -29
- data/lib/audited/version.rb +1 -1
- data/lib/audited.rb +2 -1
- data/lib/generators/audited/templates/add_version_to_auditable_index.rb +21 -0
- data/lib/generators/audited/templates/install.rb +1 -1
- data/lib/generators/audited/upgrade_generator.rb +4 -0
- data/spec/audited/audit_spec.rb +88 -21
- data/spec/audited/auditor_spec.rb +207 -54
- data/spec/audited/sweeper_spec.rb +13 -5
- data/spec/rails_app/app/controllers/application_controller.rb +2 -0
- data/spec/rails_app/config/application.rb +5 -0
- data/spec/rails_app/config/database.yml +1 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/active_record/models.rb +7 -0
- data/spec/support/active_record/schema.rb +3 -2
- data/test/db/version_6.rb +2 -0
- data/test/test_helper.rb +1 -2
- data/test/upgrade_generator_test.rb +10 -0
- metadata +54 -19
- data/gemfiles/rails40.gemfile +0 -9
- data/gemfiles/rails41.gemfile +0 -8
data/lib/audited/auditor.rb
CHANGED
|
@@ -55,7 +55,7 @@ module Audited
|
|
|
55
55
|
|
|
56
56
|
class_attribute :audit_associated_with, instance_writer: false
|
|
57
57
|
class_attribute :audited_options, instance_writer: false
|
|
58
|
-
attr_accessor :
|
|
58
|
+
attr_accessor :audit_version, :audit_comment
|
|
59
59
|
|
|
60
60
|
self.audited_options = options
|
|
61
61
|
normalize_audited_options
|
|
@@ -90,6 +90,16 @@ module Audited
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
module AuditedInstanceMethods
|
|
93
|
+
# Deprecate version attribute in favor of audit_version attribute – preparing for eventual removal.
|
|
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
|
|
102
|
+
|
|
93
103
|
# Temporarily turns off auditing while saving.
|
|
94
104
|
def save_without_auditing
|
|
95
105
|
without_auditing { save }
|
|
@@ -105,6 +115,21 @@ module Audited
|
|
|
105
115
|
self.class.without_auditing(&block)
|
|
106
116
|
end
|
|
107
117
|
|
|
118
|
+
# Temporarily turns on auditing while saving.
|
|
119
|
+
def save_with_auditing
|
|
120
|
+
with_auditing { save }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Executes the block with the auditing callbacks enabled.
|
|
124
|
+
#
|
|
125
|
+
# @foo.with_auditing do
|
|
126
|
+
# @foo.save
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
def with_auditing(&block)
|
|
130
|
+
self.class.with_auditing(&block)
|
|
131
|
+
end
|
|
132
|
+
|
|
108
133
|
# Gets an array of the revisions available
|
|
109
134
|
#
|
|
110
135
|
# user.revisions.each do |revision|
|
|
@@ -142,7 +167,16 @@ module Audited
|
|
|
142
167
|
|
|
143
168
|
# List of attributes that are audited.
|
|
144
169
|
def audited_attributes
|
|
145
|
-
attributes.except(*non_audited_columns)
|
|
170
|
+
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
|
171
|
+
normalize_enum_changes(audited_attributes)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Returns a list combined of record audits and associated audits.
|
|
175
|
+
def own_and_associated_audits
|
|
176
|
+
Audited.audit_class.unscoped
|
|
177
|
+
.where('(auditable_type = :type AND auditable_id = :id) OR (associated_type = :type AND associated_id = :id)',
|
|
178
|
+
type: self.class.name, id: id)
|
|
179
|
+
.order(created_at: :desc)
|
|
146
180
|
end
|
|
147
181
|
|
|
148
182
|
# Combine multiple audits into one.
|
|
@@ -159,18 +193,9 @@ module Audited
|
|
|
159
193
|
|
|
160
194
|
protected
|
|
161
195
|
|
|
162
|
-
def non_audited_columns
|
|
163
|
-
self.class.non_audited_columns
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def audited_columns
|
|
167
|
-
self.class.audited_columns
|
|
168
|
-
end
|
|
169
|
-
|
|
170
196
|
def revision_with(attributes)
|
|
171
197
|
dup.tap do |revision|
|
|
172
198
|
revision.id = id
|
|
173
|
-
revision.send :instance_variable_set, '@attributes', self.attributes if rails_below?('4.2.0')
|
|
174
199
|
revision.send :instance_variable_set, '@new_record', destroyed?
|
|
175
200
|
revision.send :instance_variable_set, '@persisted', !destroyed?
|
|
176
201
|
revision.send :instance_variable_set, '@readonly', false
|
|
@@ -193,25 +218,45 @@ module Audited
|
|
|
193
218
|
end
|
|
194
219
|
end
|
|
195
220
|
|
|
196
|
-
def rails_below?(rails_version)
|
|
197
|
-
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
221
|
private
|
|
201
222
|
|
|
202
223
|
def audited_changes
|
|
203
224
|
all_changes = respond_to?(:changes_to_save) ? changes_to_save : changes
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
225
|
+
filtered_changes = \
|
|
226
|
+
if audited_options[:only].present?
|
|
227
|
+
all_changes.slice(*self.class.audited_columns)
|
|
228
|
+
else
|
|
229
|
+
all_changes.except(*self.class.non_audited_columns)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
filtered_changes = normalize_enum_changes(filtered_changes)
|
|
233
|
+
filtered_changes.to_hash
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def normalize_enum_changes(changes)
|
|
237
|
+
self.class.defined_enums.each do |name, values|
|
|
238
|
+
if changes.has_key?(name)
|
|
239
|
+
changes[name] = \
|
|
240
|
+
if changes[name].is_a?(Array)
|
|
241
|
+
changes[name].map { |v| values[v] }
|
|
242
|
+
elsif rails_below?('5.0')
|
|
243
|
+
changes[name]
|
|
244
|
+
else
|
|
245
|
+
values[changes[name]]
|
|
246
|
+
end
|
|
247
|
+
end
|
|
208
248
|
end
|
|
249
|
+
changes
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def rails_below?(rails_version)
|
|
253
|
+
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
|
209
254
|
end
|
|
210
255
|
|
|
211
256
|
def audits_to(version = nil)
|
|
212
257
|
if version == :previous
|
|
213
|
-
version = if self.
|
|
214
|
-
self.
|
|
258
|
+
version = if self.audit_version
|
|
259
|
+
self.audit_version - 1
|
|
215
260
|
else
|
|
216
261
|
previous = audits.descending.offset(1).first
|
|
217
262
|
previous ? previous.version : 1
|
|
@@ -226,7 +271,7 @@ module Audited
|
|
|
226
271
|
end
|
|
227
272
|
|
|
228
273
|
def audit_update
|
|
229
|
-
unless (changes = audited_changes).empty? && audit_comment.blank?
|
|
274
|
+
unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
|
230
275
|
write_audit(action: 'update', audited_changes: changes,
|
|
231
276
|
comment: audit_comment)
|
|
232
277
|
end
|
|
@@ -290,17 +335,12 @@ module Audited
|
|
|
290
335
|
|
|
291
336
|
def run_conditional_check(condition, matching: true)
|
|
292
337
|
return true if condition.blank?
|
|
293
|
-
|
|
294
338
|
return condition.call(self) == matching if condition.respond_to?(:call)
|
|
295
|
-
return send(condition) == matching if respond_to?(condition.to_sym)
|
|
339
|
+
return send(condition) == matching if respond_to?(condition.to_sym, true)
|
|
296
340
|
|
|
297
341
|
true
|
|
298
342
|
end
|
|
299
343
|
|
|
300
|
-
def auditing_enabled=(val)
|
|
301
|
-
self.class.auditing_enabled = val
|
|
302
|
-
end
|
|
303
|
-
|
|
304
344
|
def reconstruct_attributes(audits)
|
|
305
345
|
attributes = {}
|
|
306
346
|
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
|
@@ -338,6 +378,20 @@ module Audited
|
|
|
338
378
|
enable_auditing if auditing_was_enabled
|
|
339
379
|
end
|
|
340
380
|
|
|
381
|
+
# Executes the block with auditing enabled.
|
|
382
|
+
#
|
|
383
|
+
# Foo.with_auditing do
|
|
384
|
+
# @foo.save
|
|
385
|
+
# end
|
|
386
|
+
#
|
|
387
|
+
def with_auditing
|
|
388
|
+
auditing_was_enabled = auditing_enabled
|
|
389
|
+
enable_auditing
|
|
390
|
+
yield
|
|
391
|
+
ensure
|
|
392
|
+
disable_auditing unless auditing_was_enabled
|
|
393
|
+
end
|
|
394
|
+
|
|
341
395
|
def disable_auditing
|
|
342
396
|
self.auditing_enabled = false
|
|
343
397
|
end
|
|
@@ -355,7 +409,7 @@ module Audited
|
|
|
355
409
|
end
|
|
356
410
|
|
|
357
411
|
def auditing_enabled
|
|
358
|
-
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
|
412
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true) && Audited.auditing_enabled
|
|
359
413
|
end
|
|
360
414
|
|
|
361
415
|
def auditing_enabled=(val)
|
data/lib/audited/version.rb
CHANGED
data/lib/audited.rb
CHANGED
|
@@ -2,7 +2,7 @@ require 'active_record'
|
|
|
2
2
|
|
|
3
3
|
module Audited
|
|
4
4
|
class << self
|
|
5
|
-
attr_accessor :ignored_attributes, :current_user_method, :max_audits
|
|
5
|
+
attr_accessor :ignored_attributes, :current_user_method, :max_audits, :auditing_enabled
|
|
6
6
|
attr_writer :audit_class
|
|
7
7
|
|
|
8
8
|
def audit_class
|
|
@@ -21,6 +21,7 @@ module Audited
|
|
|
21
21
|
@ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
|
|
22
22
|
|
|
23
23
|
@current_user_method = :current_user
|
|
24
|
+
@auditing_enabled = true
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
require 'audited/auditor'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
|
2
|
+
def self.up
|
|
3
|
+
if index_exists?(:audits, [:auditable_type, :auditable_id], name: index_name)
|
|
4
|
+
remove_index :audits, name: index_name
|
|
5
|
+
add_index :audits, [:auditable_type, :auditable_id, :version], name: index_name
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.down
|
|
10
|
+
if index_exists?(:audits, [:auditable_type, :auditable_id, :version], name: index_name)
|
|
11
|
+
remove_index :audits, name: index_name
|
|
12
|
+
add_index :audits, [:auditable_type, :auditable_id], name: index_name
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def index_name
|
|
19
|
+
'auditable_index'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -17,7 +17,7 @@ class <%= migration_class_name %> < <%= migration_parent %>
|
|
|
17
17
|
t.column :created_at, :datetime
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
add_index :audits, [:auditable_type, :auditable_id], :name => 'auditable_index'
|
|
20
|
+
add_index :audits, [:auditable_type, :auditable_id, :version], :name => 'auditable_index'
|
|
21
21
|
add_index :audits, [:associated_type, :associated_id], :name => 'associated_index'
|
|
22
22
|
add_index :audits, [:user_id, :user_type], :name => 'user_index'
|
|
23
23
|
add_index :audits, :request_uuid
|
|
@@ -58,6 +58,10 @@ module Audited
|
|
|
58
58
|
if indexes.any? { |i| i.columns == %w[associated_id associated_type] }
|
|
59
59
|
yield :revert_polymorphic_indexes_order
|
|
60
60
|
end
|
|
61
|
+
|
|
62
|
+
if indexes.any? { |i| i.columns == %w[auditable_type auditable_id] }
|
|
63
|
+
yield :add_version_to_auditable_index
|
|
64
|
+
end
|
|
61
65
|
end
|
|
62
66
|
end
|
|
63
67
|
end
|
data/spec/audited/audit_spec.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
|
|
3
|
+
SingleCov.covered!
|
|
4
|
+
|
|
3
5
|
describe Audited::Audit do
|
|
4
6
|
let(:user) { Models::ActiveRecord::User.new name: "Testing" }
|
|
5
7
|
|
|
@@ -38,43 +40,64 @@ describe Audited::Audit do
|
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
context "when a custom audit class is not configured" do
|
|
44
|
+
it "should default to #{described_class}" do
|
|
45
|
+
TempModel.audited
|
|
46
|
+
|
|
47
|
+
record = TempModel.create
|
|
48
|
+
|
|
49
|
+
audit = record.audits.first
|
|
50
|
+
expect(audit).to be_a Audited::Audit
|
|
51
|
+
expect(audit.respond_to?(:custom_method)).to be false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#audited_changes" do
|
|
57
|
+
let(:audit) { Audited.audit_class.new }
|
|
58
|
+
|
|
59
|
+
it "can unserialize yaml from text columns" do
|
|
60
|
+
audit.audited_changes = {foo: "bar"}
|
|
61
|
+
expect(audit.audited_changes).to eq foo: "bar"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "does not unserialize from binary columns" do
|
|
65
|
+
allow(Audited.audit_class.columns_hash["audited_changes"]).to receive(:type).and_return("foo")
|
|
66
|
+
audit.audited_changes = {foo: "bar"}
|
|
67
|
+
expect(audit.audited_changes).to eq "{:foo=>\"bar\"}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "#undo" do
|
|
72
|
+
let(:user) { Models::ActiveRecord::User.create(name: "John") }
|
|
73
|
+
|
|
74
|
+
it "undos changes" do
|
|
43
75
|
user.update_attribute(:name, 'Joe')
|
|
44
76
|
user.audits.last.undo
|
|
45
77
|
user.reload
|
|
46
|
-
|
|
47
78
|
expect(user.name).to eq("John")
|
|
48
79
|
end
|
|
49
80
|
|
|
50
|
-
it "
|
|
51
|
-
user = Models::ActiveRecord::User.create(name: "John")
|
|
81
|
+
it "undos destroy" do
|
|
52
82
|
user.destroy
|
|
53
83
|
user.audits.last.undo
|
|
54
84
|
user = Models::ActiveRecord::User.find_by(name: "John")
|
|
55
85
|
expect(user.name).to eq("John")
|
|
56
86
|
end
|
|
57
87
|
|
|
58
|
-
it "
|
|
59
|
-
user
|
|
88
|
+
it "undos creation" do
|
|
89
|
+
user # trigger create
|
|
60
90
|
expect {user.audits.last.undo}.to change(Models::ActiveRecord::User, :count).by(-1)
|
|
61
91
|
end
|
|
62
92
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
record = TempModel.create
|
|
68
|
-
|
|
69
|
-
audit = record.audits.first
|
|
70
|
-
expect(audit).to be_a Audited::Audit
|
|
71
|
-
expect(audit.respond_to?(:custom_method)).to be false
|
|
72
|
-
end
|
|
93
|
+
it "fails when trying to undo unknown" do
|
|
94
|
+
audit = user.audits.last
|
|
95
|
+
audit.action = 'oops'
|
|
96
|
+
expect { audit.undo }.to raise_error("invalid action given oops")
|
|
73
97
|
end
|
|
74
98
|
end
|
|
75
99
|
|
|
76
100
|
describe "user=" do
|
|
77
|
-
|
|
78
101
|
it "should be able to set the user to a model object" do
|
|
79
102
|
subject.user = user
|
|
80
103
|
expect(subject.user).to eq(user)
|
|
@@ -110,11 +133,9 @@ describe Audited::Audit do
|
|
|
110
133
|
subject.user = user
|
|
111
134
|
expect(subject.username).to be_nil
|
|
112
135
|
end
|
|
113
|
-
|
|
114
136
|
end
|
|
115
137
|
|
|
116
138
|
describe "revision" do
|
|
117
|
-
|
|
118
139
|
it "should recreate attributes" do
|
|
119
140
|
user = Models::ActiveRecord::User.create name: "1"
|
|
120
141
|
5.times {|i| user.update_attribute :name, (i + 2).to_s }
|
|
@@ -148,6 +169,34 @@ describe Audited::Audit do
|
|
|
148
169
|
end
|
|
149
170
|
end
|
|
150
171
|
|
|
172
|
+
describe ".collection_cache_key" do
|
|
173
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
|
174
|
+
it "uses created at" do
|
|
175
|
+
Audited::Audit.delete_all
|
|
176
|
+
audit = Models::ActiveRecord::User.create(name: "John").audits.last
|
|
177
|
+
audit.update_columns(created_at: Time.parse('2018-01-01'))
|
|
178
|
+
expect(Audited::Audit.collection_cache_key).to match(/-20180101\d+$/)
|
|
179
|
+
end
|
|
180
|
+
else
|
|
181
|
+
it "is not defined" do
|
|
182
|
+
expect { Audited::Audit.collection_cache_key }.to raise_error(NoMethodError)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
describe ".assign_revision_attributes" do
|
|
188
|
+
it "dups when frozen" do
|
|
189
|
+
user.freeze
|
|
190
|
+
assigned = Audited::Audit.assign_revision_attributes(user, name: "Bar")
|
|
191
|
+
expect(assigned.name).to eq "Bar"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "ignores unassignable attributes" do
|
|
195
|
+
assigned = Audited::Audit.assign_revision_attributes(user, oops: "Bar")
|
|
196
|
+
expect(assigned.name).to eq "Testing"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
151
200
|
it "should set the version number on create" do
|
|
152
201
|
user = Models::ActiveRecord::User.create! name: "Set Version Number"
|
|
153
202
|
expect(user.audits.first.version).to eq(1)
|
|
@@ -213,6 +262,25 @@ describe Audited::Audit do
|
|
|
213
262
|
end
|
|
214
263
|
end
|
|
215
264
|
|
|
265
|
+
it "should support nested as_user" do
|
|
266
|
+
Audited::Audit.as_user("sidekiq") do
|
|
267
|
+
company = Models::ActiveRecord::Company.create name: "The auditors"
|
|
268
|
+
company.name = "The Auditors, Inc"
|
|
269
|
+
company.save
|
|
270
|
+
expect(company.audits[-1].user).to eq("sidekiq")
|
|
271
|
+
|
|
272
|
+
Audited::Audit.as_user(user) do
|
|
273
|
+
company.name = "NEW Auditors, Inc"
|
|
274
|
+
company.save
|
|
275
|
+
expect(company.audits[-1].user).to eq(user)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
company.name = "LAST Auditors, Inc"
|
|
279
|
+
company.save
|
|
280
|
+
expect(company.audits[-1].user).to eq("sidekiq")
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
216
284
|
it "should record usernames" do
|
|
217
285
|
Audited::Audit.as_user(user.name) do
|
|
218
286
|
company = Models::ActiveRecord::Company.create name: "The auditors"
|
|
@@ -263,6 +331,5 @@ describe Audited::Audit do
|
|
|
263
331
|
}.to raise_exception('expected')
|
|
264
332
|
expect(Audited.store[:audited_user]).to be_nil
|
|
265
333
|
end
|
|
266
|
-
|
|
267
334
|
end
|
|
268
335
|
end
|