audited 4.9.0 → 5.4.3

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.
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