audited 4.8.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of audited might be problematic. Click here for more details.

@@ -34,6 +34,16 @@ module Audited
34
34
  # * +require_comment+ - Ensures that audit_comment is supplied before
35
35
  # any create, update or destroy operation.
36
36
  # * +max_audits+ - Limits the number of stored audits.
37
+
38
+ # * +redacted+ - Changes to these fields will be logged, but the values
39
+ # will not. This is useful, for example, if you wish to audit when a
40
+ # password is changed, without saving the actual password in the log.
41
+ # To store values as something other than '[REDACTED]', pass an argument
42
+ # to the redaction_value option.
43
+ #
44
+ # class User < ActiveRecord::Base
45
+ # audited redacted: :password, redaction_value: SecureRandom.uuid
46
+ # end
37
47
  #
38
48
  # * +if+ - Only audit the model when the given function returns true
39
49
  # * +unless+ - Only audit the model when the given function returns false
@@ -90,16 +100,8 @@ module Audited
90
100
  end
91
101
 
92
102
  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
-
103
+ REDACTED = '[REDACTED]'
104
+
103
105
  # Temporarily turns off auditing while saving.
104
106
  def save_without_auditing
105
107
  without_auditing { save }
@@ -115,6 +117,21 @@ module Audited
115
117
  self.class.without_auditing(&block)
116
118
  end
117
119
 
120
+ # Temporarily turns on auditing while saving.
121
+ def save_with_auditing
122
+ with_auditing { save }
123
+ end
124
+
125
+ # Executes the block with the auditing callbacks enabled.
126
+ #
127
+ # @foo.with_auditing do
128
+ # @foo.save
129
+ # end
130
+ #
131
+ def with_auditing(&block)
132
+ self.class.with_auditing(&block)
133
+ end
134
+
118
135
  # Gets an array of the revisions available
119
136
  #
120
137
  # user.revisions.each do |revision|
@@ -152,7 +169,8 @@ module Audited
152
169
 
153
170
  # List of attributes that are audited.
154
171
  def audited_attributes
155
- attributes.except(*non_audited_columns)
172
+ audited_attributes = attributes.except(*self.class.non_audited_columns)
173
+ normalize_enum_changes(audited_attributes)
156
174
  end
157
175
 
158
176
  # Returns a list combined of record audits and associated audits.
@@ -177,18 +195,9 @@ module Audited
177
195
 
178
196
  protected
179
197
 
180
- def non_audited_columns
181
- self.class.non_audited_columns
182
- end
183
-
184
- def audited_columns
185
- self.class.audited_columns
186
- end
187
-
188
198
  def revision_with(attributes)
189
199
  dup.tap do |revision|
190
200
  revision.id = id
191
- revision.send :instance_variable_set, '@attributes', self.attributes if rails_below?('4.2.0')
192
201
  revision.send :instance_variable_set, '@new_record', destroyed?
193
202
  revision.send :instance_variable_set, '@persisted', !destroyed?
194
203
  revision.send :instance_variable_set, '@readonly', false
@@ -211,19 +220,56 @@ module Audited
211
220
  end
212
221
  end
213
222
 
214
- def rails_below?(rails_version)
215
- Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
216
- end
217
-
218
223
  private
219
224
 
220
225
  def audited_changes
221
226
  all_changes = respond_to?(:changes_to_save) ? changes_to_save : changes
222
- if audited_options[:only].present?
223
- all_changes.slice(*audited_columns)
224
- else
225
- all_changes.except(*non_audited_columns)
227
+ filtered_changes = \
228
+ if audited_options[:only].present?
229
+ all_changes.slice(*self.class.audited_columns)
230
+ else
231
+ all_changes.except(*self.class.non_audited_columns)
232
+ end
233
+
234
+ filtered_changes = redact_values(filtered_changes)
235
+ filtered_changes = normalize_enum_changes(filtered_changes)
236
+ filtered_changes.to_hash
237
+ end
238
+
239
+ def normalize_enum_changes(changes)
240
+ self.class.defined_enums.each do |name, values|
241
+ if changes.has_key?(name)
242
+ changes[name] = \
243
+ if changes[name].is_a?(Array)
244
+ changes[name].map { |v| values[v] }
245
+ elsif rails_below?('5.0')
246
+ changes[name]
247
+ else
248
+ values[changes[name]]
249
+ end
250
+ end
226
251
  end
252
+ changes
253
+ end
254
+
255
+ def redact_values(filtered_changes)
256
+ [audited_options[:redacted]].flatten.compact.each do |option|
257
+ changes = filtered_changes[option.to_s]
258
+ new_value = audited_options[:redaction_value] || REDACTED
259
+ if changes.is_a? Array
260
+ values = changes.map { new_value }
261
+ else
262
+ values = new_value
263
+ end
264
+ hash = Hash[option.to_s, values]
265
+ filtered_changes.merge!(hash)
266
+ end
267
+
268
+ filtered_changes
269
+ end
270
+
271
+ def rails_below?(rails_version)
272
+ Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
227
273
  end
228
274
 
229
275
  def audits_to(version = nil)
@@ -244,7 +290,7 @@ module Audited
244
290
  end
245
291
 
246
292
  def audit_update
247
- unless (changes = audited_changes).empty? && audit_comment.blank?
293
+ unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
248
294
  write_audit(action: 'update', audited_changes: changes,
249
295
  comment: audit_comment)
250
296
  end
@@ -314,10 +360,6 @@ module Audited
314
360
  true
315
361
  end
316
362
 
317
- def auditing_enabled=(val)
318
- self.class.auditing_enabled = val
319
- end
320
-
321
363
  def reconstruct_attributes(audits)
322
364
  attributes = {}
323
365
  audits.each { |audit| attributes.merge!(audit.new_attributes) }
@@ -355,6 +397,20 @@ module Audited
355
397
  enable_auditing if auditing_was_enabled
356
398
  end
357
399
 
400
+ # Executes the block with auditing enabled.
401
+ #
402
+ # Foo.with_auditing do
403
+ # @foo.save
404
+ # end
405
+ #
406
+ def with_auditing
407
+ auditing_was_enabled = auditing_enabled
408
+ enable_auditing
409
+ yield
410
+ ensure
411
+ disable_auditing unless auditing_was_enabled
412
+ end
413
+
358
414
  def disable_auditing
359
415
  self.auditing_enabled = false
360
416
  end
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.8.0"
2
+ VERSION = "4.10.0"
3
3
  end
@@ -1,5 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
+ SingleCov.covered! uncovered: 1 # Rails version check
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::YAMLIfTextColumnType).to receive(:text_column?).and_return(false)
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.zone.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)
@@ -282,6 +331,5 @@ describe Audited::Audit do
282
331
  }.to raise_exception('expected')
283
332
  expect(Audited.store[:audited_user]).to be_nil
284
333
  end
285
-
286
334
  end
287
335
  end