audited 4.9.0 → 5.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/buildlight.yml +15 -0
  3. data/.github/workflows/ci.yml +145 -0
  4. data/.github/workflows/publish_gem.yml +28 -0
  5. data/.standard.yml +5 -0
  6. data/Appraisals +35 -16
  7. data/CHANGELOG.md +162 -1
  8. data/Gemfile +1 -1
  9. data/README.md +73 -18
  10. data/Rakefile +5 -7
  11. data/gemfiles/rails50.gemfile +2 -0
  12. data/gemfiles/rails51.gemfile +2 -0
  13. data/gemfiles/rails52.gemfile +3 -1
  14. data/gemfiles/rails60.gemfile +1 -1
  15. data/gemfiles/rails61.gemfile +10 -0
  16. data/gemfiles/rails70.gemfile +10 -0
  17. data/gemfiles/rails71.gemfile +10 -0
  18. data/lib/audited/audit.rb +41 -29
  19. data/lib/audited/auditor.rb +134 -56
  20. data/lib/audited/railtie.rb +16 -0
  21. data/lib/audited/rspec_matchers.rb +5 -3
  22. data/lib/audited/sweeper.rb +3 -10
  23. data/lib/audited/version.rb +3 -1
  24. data/lib/audited-rspec.rb +3 -1
  25. data/lib/audited.rb +31 -9
  26. data/lib/generators/audited/install_generator.rb +9 -7
  27. data/lib/generators/audited/migration.rb +12 -2
  28. data/lib/generators/audited/migration_helper.rb +3 -1
  29. data/lib/generators/audited/templates/add_association_to_audits.rb +2 -0
  30. data/lib/generators/audited/templates/add_comment_to_audits.rb +2 -0
  31. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +2 -0
  32. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +2 -0
  33. data/lib/generators/audited/templates/add_version_to_auditable_index.rb +2 -0
  34. data/lib/generators/audited/templates/install.rb +2 -0
  35. data/lib/generators/audited/templates/rename_association_to_associated.rb +2 -0
  36. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +2 -0
  37. data/lib/generators/audited/templates/rename_parent_to_association.rb +2 -0
  38. data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +2 -0
  39. data/lib/generators/audited/upgrade_generator.rb +16 -14
  40. data/spec/audited/audit_spec.rb +70 -48
  41. data/spec/audited/auditor_spec.rb +477 -246
  42. data/spec/audited/sweeper_spec.rb +19 -18
  43. data/spec/audited_spec.rb +14 -0
  44. data/spec/audited_spec_helpers.rb +11 -7
  45. data/spec/rails_app/app/assets/config/manifest.js +2 -0
  46. data/spec/rails_app/config/application.rb +32 -3
  47. data/spec/rails_app/config/database.yml +3 -2
  48. data/spec/rails_app/config/environment.rb +1 -1
  49. data/spec/rails_app/config/environments/test.rb +10 -5
  50. data/spec/rails_app/config/initializers/secret_token.rb +2 -2
  51. data/spec/spec_helper.rb +14 -14
  52. data/spec/support/active_record/models.rb +62 -13
  53. data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +1 -2
  54. data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +1 -2
  55. data/spec/support/active_record/schema.rb +26 -19
  56. data/test/db/version_1.rb +2 -2
  57. data/test/db/version_2.rb +2 -2
  58. data/test/db/version_3.rb +2 -3
  59. data/test/db/version_4.rb +2 -3
  60. data/test/db/version_5.rb +0 -1
  61. data/test/db/version_6.rb +1 -1
  62. data/test/install_generator_test.rb +18 -19
  63. data/test/test_helper.rb +5 -5
  64. data/test/upgrade_generator_test.rb +13 -18
  65. metadata +49 -31
  66. data/.rubocop.yml +0 -25
  67. data/.travis.yml +0 -58
  68. data/gemfiles/rails42.gemfile +0 -11
  69. data/spec/rails_app/app/controllers/application_controller.rb +0 -2
  70. data/spec/rails_app/config/environments/development.rb +0 -21
  71. data/spec/rails_app/config/environments/production.rb +0 -35
@@ -6,5 +6,7 @@ gem "rails", "~> 5.1.4"
6
6
  gem "mysql2", ">= 0.3.18", "< 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: "../"
@@ -2,9 +2,11 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", ">= 5.2.0", "< 5.3"
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: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", ">= 6.0.0.rc1", "< 6.1"
5
+ gem "rails", ">= 6.0.0", "< 6.1"
6
6
  gem "mysql2", ">= 0.4.4"
7
7
  gem "pg", ">= 0.18", "< 2.0"
8
8
  gem "sqlite3", "~> 1.4"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", ">= 6.1.0", "< 6.2"
6
+ gem "mysql2", ">= 0.4.4"
7
+ gem "pg", ">= 1.1", "< 2.0"
8
+ gem "sqlite3", "~> 1.4"
9
+
10
+ gemspec name: "audited", path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", ">= 7.0.0", "< 7.1"
6
+ gem "mysql2", ">= 0.4.4"
7
+ gem "pg", ">= 1.1"
8
+ gem "sqlite3", ">= 1.4"
9
+
10
+ gemspec name: "audited", path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", ">= 7.1.0.beta1", "< 7.2"
6
+ gem "mysql2", ">= 0.4.4"
7
+ gem "pg", ">= 1.1"
8
+ gem "sqlite3", ">= 1.4"
9
+
10
+ gemspec name: "audited", path: "../"
data/lib/audited/audit.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'set'
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 Audited.audit_class.columns_hash["audited_changes"].type.to_s == "text"
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 Audited.audit_class.columns_hash["audited_changes"].type.to_s == "text"
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, polymorphic: true
38
- belongs_to :user, polymorphic: true
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
- serialize :audited_changes, YAMLIfTextColumnType
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, ->{ reorder(version: :asc) }
49
- scope :descending, ->{ reorder(version: :desc)}
50
- scope :creates, ->{ where(action: 'create')}
51
- scope :updates, ->{ where(action: 'update')}
52
- scope :destroys, ->{ where(action: 'destroy')}
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, ->(date_or_time){ where("created_at <= ?", date_or_time) }
55
- scope :from_version, ->(version){ where('version >= ?', version) }
56
- scope :to_version, ->(version){ where('version <= ?', 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 || {}).inject({}.with_indifferent_access) do |attrs, (attr, values)|
75
- attrs[attr] = values.is_a?(Array) ? values.last : values
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 || {}).inject({}.with_indifferent_access) do |attrs, (attr, values)|
83
- attrs[attr] = Array(values).first
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 'create'
99
+ when "create"
93
100
  # destroys a newly created record
94
101
  auditable.destroy!
95
- when 'destroy'
102
+ when "destroy"
96
103
  # creates a new record with the destroyed record attributes
97
104
  auditable_type.constantize.create!(audited_changes)
98
- when 'update'
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
- end
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
- max = self.class.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
172
- self.version = max + 1
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
@@ -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 #:nodoc:
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, instance_writer: false
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 if audited_options[:on].include?(:create)
74
- before_update :audit_update if audited_options[:on].include?(: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
- # 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
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 || self.audits.last.version >= version
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
- .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)
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
- combine_target.save!
190
- audits_to_combine.unscope(:limit).where("version < ?", combine_target.version).delete_all
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, '@new_record', destroyed?
200
- revision.send :instance_variable_set, '@persisted', !destroyed?
201
- revision.send :instance_variable_set, '@readonly', false
202
- revision.send :instance_variable_set, '@destroyed', false
203
- revision.send :instance_variable_set, '@_destroyed', false
204
- revision.send :instance_variable_set, '@marked_for_destruction', false
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 = respond_to?(:changes_to_save) ? changes_to_save : 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?('5.0')
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 self.audit_version
259
- self.audit_version - 1
260
- else
261
- previous = audits.descending.offset(1).first
262
- previous ? previous.version : 1
263
- end
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: 'create', audited_changes: audited_attributes,
270
- comment: audit_comment)
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: 'update', audited_changes: changes,
276
- comment: audit_comment)
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
- write_audit(action: 'destroy', audited_changes: audited_attributes,
282
- comment: audit_comment) unless new_record?
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] != 'create'
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, "Comment can't be blank!") unless audit_comment.present?
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
- ((audited_options[:on].include?(:create) && self.new_record?) ||
307
- (audited_options[:on].include?(:update) && self.persisted? && self.changed?))
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, "Comment can't be blank!")
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
- return run_conditional_check(audited_options[:if]) &&
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 # InstanceMethods
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 = auditing_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 = auditing_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
- Audited.store.fetch("#{table_name}_auditing_enabled", true) && Audited.auditing_enabled
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 ', '}" if @options.key?(:only)
82
- description += " except => #{@options[:except].join(', ')}" if @options.key?(:except)
83
- description += " requires audit_comment" if @options.key?(:comment_required)
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
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Audited
2
- VERSION = "4.9.0"
4
+ VERSION = "5.4.3"
3
5
  end