audited 4.5.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.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +25 -0
- data/.travis.yml +37 -16
- data/Appraisals +33 -11
- data/CHANGELOG.md +151 -0
- data/README.md +125 -39
- data/gemfiles/rails42.gemfile +4 -1
- data/gemfiles/rails50.gemfile +4 -1
- data/gemfiles/rails51.gemfile +5 -2
- data/gemfiles/rails52.gemfile +10 -0
- data/gemfiles/rails60.gemfile +10 -0
- data/gemfiles/rails61.gemfile +10 -0
- data/lib/audited.rb +4 -2
- data/lib/audited/audit.rb +39 -14
- data/lib/audited/auditor.rb +223 -72
- data/lib/audited/rspec_matchers.rb +70 -21
- data/lib/audited/version.rb +1 -1
- data/lib/generators/audited/templates/add_version_to_auditable_index.rb +21 -0
- data/lib/generators/audited/templates/install.rb +2 -2
- data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +20 -0
- data/lib/generators/audited/upgrade_generator.rb +9 -0
- data/spec/audited/audit_spec.rb +93 -4
- data/spec/audited/auditor_spec.rb +473 -57
- data/spec/audited/rspec_matchers_spec.rb +69 -0
- data/spec/audited/sweeper_spec.rb +15 -6
- data/spec/audited_spec_helpers.rb +16 -2
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- 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 +4 -1
- data/spec/support/active_record/models.rb +51 -4
- data/spec/support/active_record/schema.rb +4 -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 +62 -22
- data/gemfiles/rails40.gemfile +0 -9
- data/gemfiles/rails41.gemfile +0 -8
data/gemfiles/rails42.gemfile
CHANGED
data/gemfiles/rails50.gemfile
CHANGED
data/gemfiles/rails51.gemfile
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "rails", "
|
5
|
+
gem "rails", "~> 5.1.4"
|
6
|
+
gem "mysql2", ">= 0.3.18", "< 0.6.0"
|
7
|
+
gem "pg", ">= 0.18", "< 2.0"
|
8
|
+
gem "sqlite3", "~> 1.3.6"
|
6
9
|
|
7
|
-
gemspec :
|
10
|
+
gemspec name: "audited", path: "../"
|
data/lib/audited.rb
CHANGED
@@ -2,10 +2,11 @@ require 'active_record'
|
|
2
2
|
|
3
3
|
module Audited
|
4
4
|
class << self
|
5
|
-
attr_accessor :ignored_attributes, :current_user_method, :
|
5
|
+
attr_accessor :ignored_attributes, :current_user_method, :max_audits, :auditing_enabled
|
6
|
+
attr_writer :audit_class
|
6
7
|
|
7
8
|
def audit_class
|
8
|
-
@audit_class
|
9
|
+
@audit_class ||= Audit
|
9
10
|
end
|
10
11
|
|
11
12
|
def store
|
@@ -20,6 +21,7 @@ module Audited
|
|
20
21
|
@ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
|
21
22
|
|
22
23
|
@current_user_method = :current_user
|
24
|
+
@auditing_enabled = true
|
23
25
|
end
|
24
26
|
|
25
27
|
require 'audited/auditor'
|
data/lib/audited/audit.rb
CHANGED
@@ -16,7 +16,7 @@ module Audited
|
|
16
16
|
class YAMLIfTextColumnType
|
17
17
|
class << self
|
18
18
|
def load(obj)
|
19
|
-
if
|
19
|
+
if text_column?
|
20
20
|
ActiveRecord::Coders::YAMLColumn.new(Object).load(obj)
|
21
21
|
else
|
22
22
|
obj
|
@@ -24,12 +24,16 @@ module Audited
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def dump(obj)
|
27
|
-
if
|
27
|
+
if text_column?
|
28
28
|
ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj)
|
29
29
|
else
|
30
30
|
obj
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
def text_column?
|
35
|
+
Audited.audit_class.columns_hash["audited_changes"].type.to_s == "text"
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
@@ -65,7 +69,7 @@ module Audited
|
|
65
69
|
def revision
|
66
70
|
clazz = auditable_type.constantize
|
67
71
|
(clazz.find_by_id(auditable_id) || clazz.new).tap do |m|
|
68
|
-
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(
|
72
|
+
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(audit_version: version))
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
@@ -86,6 +90,23 @@ module Audited
|
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
93
|
+
# Allows user to undo changes
|
94
|
+
def undo
|
95
|
+
case action
|
96
|
+
when 'create'
|
97
|
+
# destroys a newly created record
|
98
|
+
auditable.destroy!
|
99
|
+
when 'destroy'
|
100
|
+
# creates a new record with the destroyed record attributes
|
101
|
+
auditable_type.constantize.create!(audited_changes)
|
102
|
+
when 'update'
|
103
|
+
# changes back attributes
|
104
|
+
auditable.update!(audited_changes.transform_values(&:first))
|
105
|
+
else
|
106
|
+
raise StandardError, "invalid action given #{action}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
89
110
|
# Allows user to be set to either a string or an ActiveRecord object
|
90
111
|
# @private
|
91
112
|
def user_as_string=(user)
|
@@ -113,21 +134,20 @@ module Audited
|
|
113
134
|
# All audits made during the block called will be recorded as made
|
114
135
|
# by +user+. This method is hopefully threadsafe, making it ideal
|
115
136
|
# for background operations that require audit information.
|
116
|
-
def self.as_user(user
|
137
|
+
def self.as_user(user)
|
138
|
+
last_audited_user = ::Audited.store[:audited_user]
|
117
139
|
::Audited.store[:audited_user] = user
|
118
140
|
yield
|
119
141
|
ensure
|
120
|
-
::Audited.store[:audited_user] =
|
142
|
+
::Audited.store[:audited_user] = last_audited_user
|
121
143
|
end
|
122
144
|
|
123
145
|
# @private
|
124
146
|
def self.reconstruct_attributes(audits)
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
block_given? ? result : attributes
|
147
|
+
audits.each_with_object({}) do |audit, all|
|
148
|
+
all.merge!(audit.new_attributes)
|
149
|
+
all[:audit_version] = audit.version
|
150
|
+
end
|
131
151
|
end
|
132
152
|
|
133
153
|
# @private
|
@@ -145,15 +165,20 @@ module Audited
|
|
145
165
|
end
|
146
166
|
|
147
167
|
# use created_at as timestamp cache key
|
148
|
-
def self.collection_cache_key(collection = all,
|
168
|
+
def self.collection_cache_key(collection = all, *)
|
149
169
|
super(collection, :created_at)
|
150
170
|
end
|
151
171
|
|
152
172
|
private
|
153
173
|
|
154
174
|
def set_version_number
|
155
|
-
|
156
|
-
|
175
|
+
if action == 'create'
|
176
|
+
self.version = 1
|
177
|
+
else
|
178
|
+
collection = Rails::VERSION::MAJOR == 6 ? self.class.unscoped : self.class
|
179
|
+
max = collection.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
|
180
|
+
self.version = max + 1
|
181
|
+
end
|
157
182
|
end
|
158
183
|
|
159
184
|
def set_audit_user
|
data/lib/audited/auditor.rb
CHANGED
@@ -33,31 +33,56 @@ module Audited
|
|
33
33
|
#
|
34
34
|
# * +require_comment+ - Ensures that audit_comment is supplied before
|
35
35
|
# any create, update or destroy operation.
|
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
|
47
|
+
#
|
48
|
+
# * +if+ - Only audit the model when the given function returns true
|
49
|
+
# * +unless+ - Only audit the model when the given function returns false
|
50
|
+
#
|
51
|
+
# class User < ActiveRecord::Base
|
52
|
+
# audited :if => :active?
|
53
|
+
#
|
54
|
+
# def active?
|
55
|
+
# self.status == 'active'
|
56
|
+
# end
|
57
|
+
# end
|
36
58
|
#
|
37
59
|
def audited(options = {})
|
38
60
|
# don't allow multiple calls
|
39
61
|
return if included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
|
40
62
|
|
41
|
-
|
63
|
+
extend Audited::Auditor::AuditedClassMethods
|
64
|
+
include Audited::Auditor::AuditedInstanceMethods
|
65
|
+
|
66
|
+
class_attribute :audit_associated_with, instance_writer: false
|
42
67
|
class_attribute :audited_options, instance_writer: false
|
68
|
+
attr_accessor :audit_version, :audit_comment
|
43
69
|
|
44
70
|
self.audited_options = options
|
45
|
-
|
71
|
+
normalize_audited_options
|
46
72
|
|
47
|
-
|
48
|
-
validates_presence_of :audit_comment, if: :auditing_enabled
|
49
|
-
before_destroy :require_comment
|
50
|
-
end
|
73
|
+
self.audit_associated_with = audited_options[:associated_with]
|
51
74
|
|
52
|
-
|
75
|
+
if audited_options[:comment_required]
|
76
|
+
validate :presence_of_audit_comment
|
77
|
+
before_destroy :require_comment if audited_options[:on].include?(:destroy)
|
78
|
+
end
|
53
79
|
|
54
|
-
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name
|
80
|
+
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name, inverse_of: :auditable
|
55
81
|
Audited.audit_class.audited_class_names << to_s
|
56
82
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
before_destroy :audit_destroy if on.empty? || on.include?(:destroy)
|
83
|
+
after_create :audit_create if audited_options[:on].include?(:create)
|
84
|
+
before_update :audit_update if audited_options[:on].include?(:update)
|
85
|
+
before_destroy :audit_destroy if audited_options[:on].include?(:destroy)
|
61
86
|
|
62
87
|
# Define and set after_audit and around_audit callbacks. This might be useful if you want
|
63
88
|
# to notify a party after the audit has been created or if you want to access the newly-created
|
@@ -66,24 +91,17 @@ module Audited
|
|
66
91
|
set_callback :audit, :after, :after_audit, if: lambda { respond_to?(:after_audit, true) }
|
67
92
|
set_callback :audit, :around, :around_audit, if: lambda { respond_to?(:around_audit, true) }
|
68
93
|
|
69
|
-
|
70
|
-
|
71
|
-
extend Audited::Auditor::AuditedClassMethods
|
72
|
-
include Audited::Auditor::AuditedInstanceMethods
|
73
|
-
|
74
|
-
self.auditing_enabled = true
|
94
|
+
enable_auditing
|
75
95
|
end
|
76
96
|
|
77
97
|
def has_associated_audits
|
78
98
|
has_many :associated_audits, as: :associated, class_name: Audited.audit_class.name
|
79
99
|
end
|
80
|
-
|
81
|
-
def default_ignored_attributes
|
82
|
-
[primary_key, inheritance_column]
|
83
|
-
end
|
84
100
|
end
|
85
101
|
|
86
102
|
module AuditedInstanceMethods
|
103
|
+
REDACTED = '[REDACTED]'
|
104
|
+
|
87
105
|
# Temporarily turns off auditing while saving.
|
88
106
|
def save_without_auditing
|
89
107
|
without_auditing { save }
|
@@ -99,6 +117,21 @@ module Audited
|
|
99
117
|
self.class.without_auditing(&block)
|
100
118
|
end
|
101
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
|
+
|
102
135
|
# Gets an array of the revisions available
|
103
136
|
#
|
104
137
|
# user.revisions.each do |revision|
|
@@ -107,18 +140,25 @@ module Audited
|
|
107
140
|
# end
|
108
141
|
#
|
109
142
|
def revisions(from_version = 1)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
143
|
+
return [] unless audits.from_version(from_version).exists?
|
144
|
+
|
145
|
+
all_audits = audits.select([:audited_changes, :version]).to_a
|
146
|
+
targeted_audits = all_audits.select { |audit| audit.version >= from_version }
|
147
|
+
|
148
|
+
previous_attributes = reconstruct_attributes(all_audits - targeted_audits)
|
149
|
+
|
150
|
+
targeted_audits.map do |audit|
|
151
|
+
previous_attributes.merge!(audit.new_attributes)
|
152
|
+
revision_with(previous_attributes.merge!(version: audit.version))
|
115
153
|
end
|
116
|
-
revisions
|
117
154
|
end
|
118
155
|
|
119
156
|
# Get a specific revision specified by the version number, or +:previous+
|
157
|
+
# Returns nil for versions greater than revisions count
|
120
158
|
def revision(version)
|
121
|
-
|
159
|
+
if version == :previous || self.audits.last.version >= version
|
160
|
+
revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
|
161
|
+
end
|
122
162
|
end
|
123
163
|
|
124
164
|
# Find the oldest revision recorded prior to the date/time provided.
|
@@ -129,23 +169,35 @@ module Audited
|
|
129
169
|
|
130
170
|
# List of attributes that are audited.
|
131
171
|
def audited_attributes
|
132
|
-
attributes.except(*non_audited_columns
|
172
|
+
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
173
|
+
normalize_enum_changes(audited_attributes)
|
133
174
|
end
|
134
175
|
|
135
|
-
|
136
|
-
|
176
|
+
# Returns a list combined of record audits and associated audits.
|
177
|
+
def own_and_associated_audits
|
178
|
+
Audited.audit_class.unscoped
|
179
|
+
.where('(auditable_type = :type AND auditable_id = :id) OR (associated_type = :type AND associated_id = :id)',
|
180
|
+
type: self.class.name, id: id)
|
181
|
+
.order(created_at: :desc)
|
137
182
|
end
|
138
183
|
|
139
|
-
|
184
|
+
# Combine multiple audits into one.
|
185
|
+
def combine_audits(audits_to_combine)
|
186
|
+
combine_target = audits_to_combine.last
|
187
|
+
combine_target.audited_changes = audits_to_combine.pluck(:audited_changes).reduce(&:merge)
|
188
|
+
combine_target.comment = "#{combine_target.comment}\nThis audit is the result of multiple audits being combined."
|
140
189
|
|
141
|
-
|
142
|
-
|
190
|
+
transaction do
|
191
|
+
combine_target.save!
|
192
|
+
audits_to_combine.unscope(:limit).where("version < ?", combine_target.version).delete_all
|
193
|
+
end
|
143
194
|
end
|
144
195
|
|
196
|
+
protected
|
197
|
+
|
145
198
|
def revision_with(attributes)
|
146
199
|
dup.tap do |revision|
|
147
200
|
revision.id = id
|
148
|
-
revision.send :instance_variable_set, '@attributes', self.attributes if rails_below?('4.2.0')
|
149
201
|
revision.send :instance_variable_set, '@new_record', destroyed?
|
150
202
|
revision.send :instance_variable_set, '@persisted', !destroyed?
|
151
203
|
revision.send :instance_variable_set, '@readonly', false
|
@@ -168,31 +220,62 @@ module Audited
|
|
168
220
|
end
|
169
221
|
end
|
170
222
|
|
171
|
-
def rails_below?(rails_version)
|
172
|
-
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
173
|
-
end
|
174
|
-
|
175
223
|
private
|
176
224
|
|
177
225
|
def audited_changes
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
226
|
+
all_changes = respond_to?(:changes_to_save) ? changes_to_save : changes
|
227
|
+
filtered_changes = \
|
228
|
+
if audited_options[:only].present?
|
229
|
+
all_changes.slice(*self.class.audited_columns)
|
182
230
|
else
|
183
|
-
|
231
|
+
all_changes.except(*self.class.non_audited_columns)
|
184
232
|
end
|
185
233
|
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
189
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)
|
190
273
|
end
|
191
274
|
|
192
275
|
def audits_to(version = nil)
|
193
276
|
if version == :previous
|
194
|
-
version = if self.
|
195
|
-
self.
|
277
|
+
version = if self.audit_version
|
278
|
+
self.audit_version - 1
|
196
279
|
else
|
197
280
|
previous = audits.descending.offset(1).first
|
198
281
|
previous ? previous.version : 1
|
@@ -207,7 +290,7 @@ module Audited
|
|
207
290
|
end
|
208
291
|
|
209
292
|
def audit_update
|
210
|
-
unless (changes = audited_changes).empty? && audit_comment.blank?
|
293
|
+
unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
211
294
|
write_audit(action: 'update', audited_changes: changes,
|
212
295
|
comment: audit_comment)
|
213
296
|
end
|
@@ -221,14 +304,41 @@ module Audited
|
|
221
304
|
def write_audit(attrs)
|
222
305
|
attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil?
|
223
306
|
self.audit_comment = nil
|
224
|
-
|
307
|
+
|
308
|
+
if auditing_enabled
|
309
|
+
run_callbacks(:audit) {
|
310
|
+
audit = audits.create(attrs)
|
311
|
+
combine_audits_if_needed if attrs[:action] != 'create'
|
312
|
+
audit
|
313
|
+
}
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def presence_of_audit_comment
|
318
|
+
if comment_required_state?
|
319
|
+
errors.add(:audit_comment, "Comment can't be blank!") unless audit_comment.present?
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def comment_required_state?
|
324
|
+
auditing_enabled &&
|
325
|
+
((audited_options[:on].include?(:create) && self.new_record?) ||
|
326
|
+
(audited_options[:on].include?(:update) && self.persisted? && self.changed?))
|
327
|
+
end
|
328
|
+
|
329
|
+
def combine_audits_if_needed
|
330
|
+
max_audits = audited_options[:max_audits]
|
331
|
+
if max_audits && (extra_count = audits.count - max_audits) > 0
|
332
|
+
audits_to_combine = audits.limit(extra_count + 1)
|
333
|
+
combine_audits(audits_to_combine)
|
334
|
+
end
|
225
335
|
end
|
226
336
|
|
227
337
|
def require_comment
|
228
338
|
if auditing_enabled && audit_comment.blank?
|
229
|
-
errors.add(:audit_comment, "Comment
|
339
|
+
errors.add(:audit_comment, "Comment can't be blank!")
|
230
340
|
return false if Rails.version.start_with?('4.')
|
231
|
-
throw
|
341
|
+
throw(:abort)
|
232
342
|
end
|
233
343
|
end
|
234
344
|
|
@@ -236,39 +346,41 @@ module Audited
|
|
236
346
|
alias_method "#{attr_name}_callback".to_sym, attr_name
|
237
347
|
end
|
238
348
|
|
239
|
-
def
|
349
|
+
def auditing_enabled
|
350
|
+
return run_conditional_check(audited_options[:if]) &&
|
351
|
+
run_conditional_check(audited_options[:unless], matching: false) &&
|
352
|
+
self.class.auditing_enabled
|
240
353
|
end
|
241
354
|
|
242
|
-
def
|
243
|
-
|
355
|
+
def run_conditional_check(condition, matching: true)
|
356
|
+
return true if condition.blank?
|
357
|
+
return condition.call(self) == matching if condition.respond_to?(:call)
|
358
|
+
return send(condition) == matching if respond_to?(condition.to_sym, true)
|
359
|
+
|
360
|
+
true
|
244
361
|
end
|
245
362
|
|
246
|
-
def
|
247
|
-
|
363
|
+
def reconstruct_attributes(audits)
|
364
|
+
attributes = {}
|
365
|
+
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
366
|
+
attributes
|
248
367
|
end
|
249
368
|
end # InstanceMethods
|
250
369
|
|
251
370
|
module AuditedClassMethods
|
252
371
|
# Returns an array of columns that are audited. See non_audited_columns
|
253
372
|
def audited_columns
|
254
|
-
|
373
|
+
@audited_columns ||= column_names - non_audited_columns
|
255
374
|
end
|
256
375
|
|
376
|
+
# We have to calculate this here since column_names may not be available when `audited` is called
|
257
377
|
def non_audited_columns
|
258
|
-
@non_audited_columns ||=
|
259
|
-
options = audited_options
|
260
|
-
if options[:only]
|
261
|
-
except = column_names - Array.wrap(options[:only]).flatten.map(&:to_s)
|
262
|
-
else
|
263
|
-
except = default_ignored_attributes + Audited.ignored_attributes
|
264
|
-
except |= Array(options[:except]).collect(&:to_s) if options[:except]
|
265
|
-
end
|
266
|
-
except
|
267
|
-
end
|
378
|
+
@non_audited_columns ||= calculate_non_audited_columns
|
268
379
|
end
|
269
380
|
|
270
381
|
def non_audited_columns=(columns)
|
271
|
-
@
|
382
|
+
@audited_columns = nil # reset cached audited columns on assignment
|
383
|
+
@non_audited_columns = columns.map(&:to_s)
|
272
384
|
end
|
273
385
|
|
274
386
|
# Executes the block with auditing disabled.
|
@@ -285,6 +397,20 @@ module Audited
|
|
285
397
|
enable_auditing if auditing_was_enabled
|
286
398
|
end
|
287
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
|
+
|
288
414
|
def disable_auditing
|
289
415
|
self.auditing_enabled = false
|
290
416
|
end
|
@@ -302,12 +428,37 @@ module Audited
|
|
302
428
|
end
|
303
429
|
|
304
430
|
def auditing_enabled
|
305
|
-
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
431
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true) && Audited.auditing_enabled
|
306
432
|
end
|
307
433
|
|
308
434
|
def auditing_enabled=(val)
|
309
435
|
Audited.store["#{table_name}_auditing_enabled"] = val
|
310
436
|
end
|
437
|
+
|
438
|
+
def default_ignored_attributes
|
439
|
+
[primary_key, inheritance_column] | Audited.ignored_attributes
|
440
|
+
end
|
441
|
+
|
442
|
+
protected
|
443
|
+
|
444
|
+
def normalize_audited_options
|
445
|
+
audited_options[:on] = Array.wrap(audited_options[:on])
|
446
|
+
audited_options[:on] = [:create, :update, :destroy] if audited_options[:on].empty?
|
447
|
+
audited_options[:only] = Array.wrap(audited_options[:only]).map(&:to_s)
|
448
|
+
audited_options[:except] = Array.wrap(audited_options[:except]).map(&:to_s)
|
449
|
+
max_audits = audited_options[:max_audits] || Audited.max_audits
|
450
|
+
audited_options[:max_audits] = Integer(max_audits).abs if max_audits
|
451
|
+
end
|
452
|
+
|
453
|
+
def calculate_non_audited_columns
|
454
|
+
if audited_options[:only].present?
|
455
|
+
(column_names | default_ignored_attributes) - audited_options[:only]
|
456
|
+
elsif audited_options[:except].present?
|
457
|
+
default_ignored_attributes | audited_options[:except]
|
458
|
+
else
|
459
|
+
default_ignored_attributes
|
460
|
+
end
|
461
|
+
end
|
311
462
|
end
|
312
463
|
end
|
313
464
|
end
|