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.
@@ -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 :version, :audit_comment
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
- if audited_options[:only].present?
205
- all_changes.slice(*audited_columns)
206
- else
207
- all_changes.except(*non_audited_columns)
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.version
214
- self.version - 1
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)
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.7.0"
2
+ VERSION = "4.9.0"
3
3
  end
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
@@ -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
- it "should undo changes" do
42
- user = Models::ActiveRecord::User.create(name: "John")
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 "should undo destroyed model" do
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 "should undo created model" do
59
- user = Models::ActiveRecord::User.create(name: "John")
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
- context "when a custom audit class is not configured" do
64
- it "should default to #{described_class}" do
65
- TempModel.audited
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