audited 4.7.0 → 5.0.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/.standard.yml +5 -0
- data/.travis.yml +35 -26
- data/Appraisals +27 -18
- data/CHANGELOG.md +106 -2
- data/Gemfile +1 -1
- data/README.md +88 -19
- data/Rakefile +6 -6
- data/gemfiles/rails50.gemfile +3 -0
- data/gemfiles/rails51.gemfile +3 -0
- data/gemfiles/rails52.gemfile +4 -2
- data/gemfiles/rails60.gemfile +10 -0
- data/gemfiles/rails61.gemfile +10 -0
- data/lib/audited-rspec.rb +3 -1
- data/lib/audited.rb +26 -8
- data/lib/audited/audit.rb +48 -43
- data/lib/audited/auditor.rb +135 -56
- data/lib/audited/railtie.rb +16 -0
- data/lib/audited/rspec_matchers.rb +5 -3
- data/lib/audited/sweeper.rb +3 -10
- data/lib/audited/version.rb +3 -1
- data/lib/generators/audited/install_generator.rb +9 -7
- data/lib/generators/audited/migration.rb +2 -0
- data/lib/generators/audited/migration_helper.rb +3 -1
- data/lib/generators/audited/templates/add_association_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +2 -0
- data/lib/generators/audited/templates/add_version_to_auditable_index.rb +23 -0
- data/lib/generators/audited/templates/install.rb +3 -1
- data/lib/generators/audited/templates/rename_association_to_associated.rb +2 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +2 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +2 -0
- data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +2 -0
- data/lib/generators/audited/upgrade_generator.rb +20 -14
- data/spec/audited/audit_spec.rb +151 -62
- data/spec/audited/auditor_spec.rb +456 -239
- data/spec/audited/sweeper_spec.rb +29 -20
- data/spec/audited_spec.rb +18 -0
- data/spec/audited_spec_helpers.rb +7 -7
- data/spec/rails_app/app/assets/config/manifest.js +2 -0
- data/spec/rails_app/config/application.rb +7 -2
- data/spec/rails_app/config/database.yml +1 -0
- data/spec/rails_app/config/environment.rb +1 -1
- data/spec/rails_app/config/environments/test.rb +5 -5
- data/spec/rails_app/config/initializers/secret_token.rb +2 -2
- data/spec/spec_helper.rb +15 -13
- data/spec/support/active_record/models.rb +37 -11
- data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +1 -2
- data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +1 -2
- data/spec/support/active_record/schema.rb +28 -20
- data/test/db/version_1.rb +2 -2
- data/test/db/version_2.rb +2 -2
- data/test/db/version_3.rb +2 -3
- data/test/db/version_4.rb +2 -3
- data/test/db/version_5.rb +0 -1
- data/test/db/version_6.rb +2 -0
- data/test/install_generator_test.rb +18 -19
- data/test/test_helper.rb +6 -7
- data/test/upgrade_generator_test.rb +22 -17
- metadata +64 -29
- data/gemfiles/rails40.gemfile +0 -9
- data/gemfiles/rails41.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -8
- data/spec/rails_app/config/environments/development.rb +0 -21
- data/spec/rails_app/config/environments/production.rb +0 -35
data/Rakefile
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "bundler/gem_helper"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
require "rake/testtask"
|
6
|
+
require "appraisal"
|
7
7
|
|
8
|
-
Bundler::GemHelper.install_tasks(name:
|
8
|
+
Bundler::GemHelper.install_tasks(name: "audited")
|
9
9
|
|
10
10
|
RSpec::Core::RakeTask.new(:spec)
|
11
11
|
|
12
12
|
Rake::TestTask.new do |t|
|
13
13
|
t.libs << "test"
|
14
|
-
t.test_files = FileList[
|
14
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
15
15
|
t.verbose = true
|
16
16
|
end
|
17
17
|
|
data/gemfiles/rails50.gemfile
CHANGED
data/gemfiles/rails51.gemfile
CHANGED
data/gemfiles/rails52.gemfile
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "rails", ">= 5.2.0
|
6
|
-
gem "mysql2", "
|
5
|
+
gem "rails", ">= 5.2.0", "< 5.3"
|
6
|
+
gem "mysql2", ">= 0.4.4", "< 0.6.0"
|
7
|
+
gem "pg", ">= 0.18", "< 2.0"
|
8
|
+
gem "sqlite3", "~> 1.3.6"
|
7
9
|
|
8
10
|
gemspec name: "audited", path: "../"
|
data/lib/audited-rspec.rb
CHANGED
data/lib/audited.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
2
4
|
|
3
5
|
module Audited
|
4
6
|
class << self
|
5
|
-
attr_accessor
|
7
|
+
attr_accessor \
|
8
|
+
:auditing_enabled,
|
9
|
+
:current_user_method,
|
10
|
+
:ignored_attributes,
|
11
|
+
:max_audits,
|
12
|
+
:store_synthesized_enums
|
6
13
|
attr_writer :audit_class
|
7
14
|
|
8
15
|
def audit_class
|
@@ -10,7 +17,13 @@ module Audited
|
|
10
17
|
end
|
11
18
|
|
12
19
|
def store
|
13
|
-
Thread.current
|
20
|
+
current_store_value = Thread.current.thread_variable_get(:audited_store)
|
21
|
+
|
22
|
+
if current_store_value.nil?
|
23
|
+
Thread.current.thread_variable_set(:audited_store, {})
|
24
|
+
else
|
25
|
+
current_store_value
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
29
|
def config
|
@@ -18,14 +31,19 @@ module Audited
|
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|
21
|
-
@ignored_attributes = %w
|
34
|
+
@ignored_attributes = %w[lock_version created_at updated_at created_on updated_on]
|
22
35
|
|
23
36
|
@current_user_method = :current_user
|
37
|
+
@auditing_enabled = true
|
38
|
+
@store_synthesized_enums = false
|
24
39
|
end
|
25
40
|
|
26
|
-
require
|
27
|
-
require
|
41
|
+
require "audited/auditor"
|
42
|
+
require "audited/audit"
|
28
43
|
|
29
|
-
|
44
|
+
ActiveSupport.on_load :active_record do
|
45
|
+
include Audited::Auditor
|
46
|
+
end
|
30
47
|
|
31
|
-
require
|
48
|
+
require "audited/sweeper"
|
49
|
+
require "audited/railtie"
|
data/lib/audited/audit.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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
|
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
|
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,
|
38
|
-
belongs_to :user,
|
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
|
@@ -45,16 +51,16 @@ module Audited
|
|
45
51
|
|
46
52
|
serialize :audited_changes, YAMLIfTextColumnType
|
47
53
|
|
48
|
-
scope :ascending,
|
49
|
-
scope :descending,
|
50
|
-
scope :creates,
|
51
|
-
scope :updates,
|
52
|
-
scope :destroys,
|
54
|
+
scope :ascending, -> { reorder(version: :asc) }
|
55
|
+
scope :descending, -> { reorder(version: :desc) }
|
56
|
+
scope :creates, -> { where(action: "create") }
|
57
|
+
scope :updates, -> { where(action: "update") }
|
58
|
+
scope :destroys, -> { where(action: "destroy") }
|
53
59
|
|
54
|
-
scope :up_until,
|
55
|
-
scope :from_version,
|
56
|
-
scope :to_version,
|
57
|
-
scope :auditable_finder, ->(auditable_id, auditable_type){ where(auditable_id: auditable_id, auditable_type: auditable_type)}
|
60
|
+
scope :up_until, ->(date_or_time) { where("created_at <= ?", date_or_time) }
|
61
|
+
scope :from_version, ->(version) { where("version >= ?", version) }
|
62
|
+
scope :to_version, ->(version) { where("version <= ?", version) }
|
63
|
+
scope :auditable_finder, ->(auditable_id, auditable_type) { where(auditable_id: auditable_id, auditable_type: auditable_type) }
|
58
64
|
# Return all audits older than the current one.
|
59
65
|
def ancestors
|
60
66
|
self.class.ascending.auditable_finder(auditable_id, auditable_type).to_version(version)
|
@@ -65,43 +71,38 @@ module Audited
|
|
65
71
|
def revision
|
66
72
|
clazz = auditable_type.constantize
|
67
73
|
(clazz.find_by_id(auditable_id) || clazz.new).tap do |m|
|
68
|
-
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(
|
74
|
+
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(audit_version: version))
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
72
78
|
# Returns a hash of the changed attributes with the new values
|
73
79
|
def new_attributes
|
74
|
-
(audited_changes || {}).
|
75
|
-
attrs[attr] =
|
76
|
-
attrs
|
80
|
+
(audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|
|
81
|
+
attrs[attr] = (action == "update" ? values.last : values)
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
80
85
|
# Returns a hash of the changed attributes with the old values
|
81
86
|
def old_attributes
|
82
|
-
(audited_changes || {}).
|
83
|
-
attrs[attr] =
|
84
|
-
|
85
|
-
attrs
|
87
|
+
(audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|
|
88
|
+
attrs[attr] = (action == "update" ? values.first : values)
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
89
92
|
# Allows user to undo changes
|
90
93
|
def undo
|
91
|
-
|
92
|
-
|
94
|
+
case action
|
95
|
+
when "create"
|
93
96
|
# destroys a newly created record
|
94
|
-
|
95
|
-
|
97
|
+
auditable.destroy!
|
98
|
+
when "destroy"
|
96
99
|
# creates a new record with the destroyed record attributes
|
97
|
-
|
98
|
-
|
100
|
+
auditable_type.constantize.create!(audited_changes)
|
101
|
+
when "update"
|
99
102
|
# changes back attributes
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
104
|
-
audited_object.save
|
103
|
+
auditable.update!(audited_changes.transform_values(&:first))
|
104
|
+
else
|
105
|
+
raise StandardError, "invalid action given #{action}"
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
@@ -132,21 +133,20 @@ module Audited
|
|
132
133
|
# All audits made during the block called will be recorded as made
|
133
134
|
# by +user+. This method is hopefully threadsafe, making it ideal
|
134
135
|
# for background operations that require audit information.
|
135
|
-
def self.as_user(user
|
136
|
+
def self.as_user(user)
|
137
|
+
last_audited_user = ::Audited.store[:audited_user]
|
136
138
|
::Audited.store[:audited_user] = user
|
137
139
|
yield
|
138
140
|
ensure
|
139
|
-
::Audited.store[:audited_user] =
|
141
|
+
::Audited.store[:audited_user] = last_audited_user
|
140
142
|
end
|
141
143
|
|
142
144
|
# @private
|
143
145
|
def self.reconstruct_attributes(audits)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
yield attributes if block_given?
|
146
|
+
audits.each_with_object({}) do |audit, all|
|
147
|
+
all.merge!(audit.new_attributes)
|
148
|
+
all[:audit_version] = audit.version
|
148
149
|
end
|
149
|
-
block_given? ? result : attributes
|
150
150
|
end
|
151
151
|
|
152
152
|
# @private
|
@@ -164,15 +164,20 @@ module Audited
|
|
164
164
|
end
|
165
165
|
|
166
166
|
# use created_at as timestamp cache key
|
167
|
-
def self.collection_cache_key(collection = all,
|
167
|
+
def self.collection_cache_key(collection = all, *)
|
168
168
|
super(collection, :created_at)
|
169
169
|
end
|
170
170
|
|
171
171
|
private
|
172
172
|
|
173
173
|
def set_version_number
|
174
|
-
|
175
|
-
|
174
|
+
if action == "create"
|
175
|
+
self.version = 1
|
176
|
+
else
|
177
|
+
collection = Rails::VERSION::MAJOR >= 6 ? self.class.unscoped : self.class
|
178
|
+
max = collection.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
|
179
|
+
self.version = max + 1
|
180
|
+
end
|
176
181
|
end
|
177
182
|
|
178
183
|
def set_audit_user
|
data/lib/audited/auditor.rb
CHANGED
@@ -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.
|
@@ -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,8 +66,8 @@ 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,
|
58
|
-
attr_accessor :
|
69
|
+
class_attribute :audited_options, instance_writer: false
|
70
|
+
attr_accessor :audit_version, :audit_comment
|
59
71
|
|
60
72
|
self.audited_options = options
|
61
73
|
normalize_audited_options
|
@@ -70,8 +82,8 @@ 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
|
74
|
-
before_update :audit_update
|
85
|
+
after_create :audit_create if audited_options[:on].include?(:create)
|
86
|
+
before_update :audit_update if audited_options[:on].include?(:update)
|
75
87
|
before_destroy :audit_destroy if audited_options[:on].include?(:destroy)
|
76
88
|
|
77
89
|
# Define and set after_audit and around_audit callbacks. This might be useful if you want
|
@@ -90,6 +102,8 @@ module Audited
|
|
90
102
|
end
|
91
103
|
|
92
104
|
module AuditedInstanceMethods
|
105
|
+
REDACTED = "[REDACTED]"
|
106
|
+
|
93
107
|
# Temporarily turns off auditing while saving.
|
94
108
|
def save_without_auditing
|
95
109
|
without_auditing { save }
|
@@ -105,6 +119,21 @@ module Audited
|
|
105
119
|
self.class.without_auditing(&block)
|
106
120
|
end
|
107
121
|
|
122
|
+
# Temporarily turns on auditing while saving.
|
123
|
+
def save_with_auditing
|
124
|
+
with_auditing { save }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Executes the block with the auditing callbacks enabled.
|
128
|
+
#
|
129
|
+
# @foo.with_auditing do
|
130
|
+
# @foo.save
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
def with_auditing(&block)
|
134
|
+
self.class.with_auditing(&block)
|
135
|
+
end
|
136
|
+
|
108
137
|
# Gets an array of the revisions available
|
109
138
|
#
|
110
139
|
# user.revisions.each do |revision|
|
@@ -115,7 +144,7 @@ module Audited
|
|
115
144
|
def revisions(from_version = 1)
|
116
145
|
return [] unless audits.from_version(from_version).exists?
|
117
146
|
|
118
|
-
all_audits = audits.select([:audited_changes, :version]).to_a
|
147
|
+
all_audits = audits.select([:audited_changes, :version, :action]).to_a
|
119
148
|
targeted_audits = all_audits.select { |audit| audit.version >= from_version }
|
120
149
|
|
121
150
|
previous_attributes = reconstruct_attributes(all_audits - targeted_audits)
|
@@ -129,7 +158,7 @@ module Audited
|
|
129
158
|
# Get a specific revision specified by the version number, or +:previous+
|
130
159
|
# Returns nil for versions greater than revisions count
|
131
160
|
def revision(version)
|
132
|
-
if version == :previous ||
|
161
|
+
if version == :previous || audits.last.version >= version
|
133
162
|
revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
|
134
163
|
end
|
135
164
|
end
|
@@ -142,7 +171,16 @@ module Audited
|
|
142
171
|
|
143
172
|
# List of attributes that are audited.
|
144
173
|
def audited_attributes
|
145
|
-
attributes.except(*non_audited_columns)
|
174
|
+
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
175
|
+
normalize_enum_changes(audited_attributes)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns a list combined of record audits and associated audits.
|
179
|
+
def own_and_associated_audits
|
180
|
+
Audited.audit_class.unscoped
|
181
|
+
.where("(auditable_type = :type AND auditable_id = :id) OR (associated_type = :type AND associated_id = :id)",
|
182
|
+
type: self.class.base_class.name, id: id)
|
183
|
+
.order(created_at: :desc)
|
146
184
|
end
|
147
185
|
|
148
186
|
# Combine multiple audits into one.
|
@@ -159,24 +197,15 @@ module Audited
|
|
159
197
|
|
160
198
|
protected
|
161
199
|
|
162
|
-
def non_audited_columns
|
163
|
-
self.class.non_audited_columns
|
164
|
-
end
|
165
|
-
|
166
|
-
def audited_columns
|
167
|
-
self.class.audited_columns
|
168
|
-
end
|
169
|
-
|
170
200
|
def revision_with(attributes)
|
171
201
|
dup.tap do |revision|
|
172
202
|
revision.id = id
|
173
|
-
revision.send :instance_variable_set,
|
174
|
-
revision.send :instance_variable_set,
|
175
|
-
revision.send :instance_variable_set,
|
176
|
-
revision.send :instance_variable_set,
|
177
|
-
revision.send :instance_variable_set,
|
178
|
-
revision.send :instance_variable_set,
|
179
|
-
revision.send :instance_variable_set, '@marked_for_destruction', false
|
203
|
+
revision.send :instance_variable_set, "@new_record", destroyed?
|
204
|
+
revision.send :instance_variable_set, "@persisted", !destroyed?
|
205
|
+
revision.send :instance_variable_set, "@readonly", false
|
206
|
+
revision.send :instance_variable_set, "@destroyed", false
|
207
|
+
revision.send :instance_variable_set, "@_destroyed", false
|
208
|
+
revision.send :instance_variable_set, "@marked_for_destruction", false
|
180
209
|
Audited.audit_class.assign_revision_attributes(revision, attributes)
|
181
210
|
|
182
211
|
# Remove any association proxies so that they will be recreated
|
@@ -193,48 +222,89 @@ module Audited
|
|
193
222
|
end
|
194
223
|
end
|
195
224
|
|
196
|
-
def rails_below?(rails_version)
|
197
|
-
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
198
|
-
end
|
199
|
-
|
200
225
|
private
|
201
226
|
|
202
227
|
def audited_changes
|
203
228
|
all_changes = respond_to?(:changes_to_save) ? changes_to_save : changes
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
229
|
+
filtered_changes = \
|
230
|
+
if audited_options[:only].present?
|
231
|
+
all_changes.slice(*self.class.audited_columns)
|
232
|
+
else
|
233
|
+
all_changes.except(*self.class.non_audited_columns)
|
234
|
+
end
|
235
|
+
|
236
|
+
filtered_changes = redact_values(filtered_changes)
|
237
|
+
filtered_changes = normalize_enum_changes(filtered_changes)
|
238
|
+
filtered_changes.to_hash
|
239
|
+
end
|
240
|
+
|
241
|
+
def normalize_enum_changes(changes)
|
242
|
+
return changes if Audited.store_synthesized_enums
|
243
|
+
|
244
|
+
self.class.defined_enums.each do |name, values|
|
245
|
+
if changes.has_key?(name)
|
246
|
+
changes[name] = \
|
247
|
+
if changes[name].is_a?(Array)
|
248
|
+
changes[name].map { |v| values[v] }
|
249
|
+
elsif rails_below?("5.0")
|
250
|
+
changes[name]
|
251
|
+
else
|
252
|
+
values[changes[name]]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
changes
|
257
|
+
end
|
258
|
+
|
259
|
+
def redact_values(filtered_changes)
|
260
|
+
[audited_options[:redacted]].flatten.compact.each do |option|
|
261
|
+
changes = filtered_changes[option.to_s]
|
262
|
+
new_value = audited_options[:redaction_value] || REDACTED
|
263
|
+
values = if changes.is_a? Array
|
264
|
+
changes.map { new_value }
|
265
|
+
else
|
266
|
+
new_value
|
267
|
+
end
|
268
|
+
hash = {option.to_s => values}
|
269
|
+
filtered_changes.merge!(hash)
|
208
270
|
end
|
271
|
+
|
272
|
+
filtered_changes
|
273
|
+
end
|
274
|
+
|
275
|
+
def rails_below?(rails_version)
|
276
|
+
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
209
277
|
end
|
210
278
|
|
211
279
|
def audits_to(version = nil)
|
212
280
|
if version == :previous
|
213
|
-
version = if
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
281
|
+
version = if audit_version
|
282
|
+
audit_version - 1
|
283
|
+
else
|
284
|
+
previous = audits.descending.offset(1).first
|
285
|
+
previous ? previous.version : 1
|
286
|
+
end
|
219
287
|
end
|
220
288
|
audits.to_version(version)
|
221
289
|
end
|
222
290
|
|
223
291
|
def audit_create
|
224
|
-
write_audit(action:
|
292
|
+
write_audit(action: "create", audited_changes: audited_attributes,
|
225
293
|
comment: audit_comment)
|
226
294
|
end
|
227
295
|
|
228
296
|
def audit_update
|
229
|
-
unless (changes = audited_changes).empty? && audit_comment.blank?
|
230
|
-
write_audit(action:
|
297
|
+
unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
298
|
+
write_audit(action: "update", audited_changes: changes,
|
231
299
|
comment: audit_comment)
|
232
300
|
end
|
233
301
|
end
|
234
302
|
|
235
303
|
def audit_destroy
|
236
|
-
|
237
|
-
|
304
|
+
unless new_record?
|
305
|
+
write_audit(action: "destroy", audited_changes: audited_attributes,
|
306
|
+
comment: audit_comment)
|
307
|
+
end
|
238
308
|
end
|
239
309
|
|
240
310
|
def write_audit(attrs)
|
@@ -244,7 +314,7 @@ module Audited
|
|
244
314
|
if auditing_enabled
|
245
315
|
run_callbacks(:audit) {
|
246
316
|
audit = audits.create(attrs)
|
247
|
-
combine_audits_if_needed if attrs[:action] !=
|
317
|
+
combine_audits_if_needed if attrs[:action] != "create"
|
248
318
|
audit
|
249
319
|
}
|
250
320
|
end
|
@@ -252,14 +322,15 @@ module Audited
|
|
252
322
|
|
253
323
|
def presence_of_audit_comment
|
254
324
|
if comment_required_state?
|
255
|
-
errors.add(:audit_comment,
|
325
|
+
errors.add(:audit_comment, :blank) unless audit_comment.present?
|
256
326
|
end
|
257
327
|
end
|
258
328
|
|
259
329
|
def comment_required_state?
|
260
330
|
auditing_enabled &&
|
261
|
-
|
262
|
-
(audited_options[:on].include?(:
|
331
|
+
audited_changes.present? &&
|
332
|
+
((audited_options[:on].include?(:create) && new_record?) ||
|
333
|
+
(audited_options[:on].include?(:update) && persisted? && changed?))
|
263
334
|
end
|
264
335
|
|
265
336
|
def combine_audits_if_needed
|
@@ -272,8 +343,7 @@ module Audited
|
|
272
343
|
|
273
344
|
def require_comment
|
274
345
|
if auditing_enabled && audit_comment.blank?
|
275
|
-
errors.add(:audit_comment,
|
276
|
-
return false if Rails.version.start_with?('4.')
|
346
|
+
errors.add(:audit_comment, :blank)
|
277
347
|
throw(:abort)
|
278
348
|
end
|
279
349
|
end
|
@@ -283,30 +353,25 @@ module Audited
|
|
283
353
|
end
|
284
354
|
|
285
355
|
def auditing_enabled
|
286
|
-
|
356
|
+
run_conditional_check(audited_options[:if]) &&
|
287
357
|
run_conditional_check(audited_options[:unless], matching: false) &&
|
288
358
|
self.class.auditing_enabled
|
289
359
|
end
|
290
360
|
|
291
361
|
def run_conditional_check(condition, matching: true)
|
292
362
|
return true if condition.blank?
|
293
|
-
|
294
363
|
return condition.call(self) == matching if condition.respond_to?(:call)
|
295
|
-
return send(condition) == matching if respond_to?(condition.to_sym)
|
364
|
+
return send(condition) == matching if respond_to?(condition.to_sym, true)
|
296
365
|
|
297
366
|
true
|
298
367
|
end
|
299
368
|
|
300
|
-
def auditing_enabled=(val)
|
301
|
-
self.class.auditing_enabled = val
|
302
|
-
end
|
303
|
-
|
304
369
|
def reconstruct_attributes(audits)
|
305
370
|
attributes = {}
|
306
371
|
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
307
372
|
attributes
|
308
373
|
end
|
309
|
-
end
|
374
|
+
end
|
310
375
|
|
311
376
|
module AuditedClassMethods
|
312
377
|
# Returns an array of columns that are audited. See non_audited_columns
|
@@ -338,6 +403,20 @@ module Audited
|
|
338
403
|
enable_auditing if auditing_was_enabled
|
339
404
|
end
|
340
405
|
|
406
|
+
# Executes the block with auditing enabled.
|
407
|
+
#
|
408
|
+
# Foo.with_auditing do
|
409
|
+
# @foo.save
|
410
|
+
# end
|
411
|
+
#
|
412
|
+
def with_auditing
|
413
|
+
auditing_was_enabled = auditing_enabled
|
414
|
+
enable_auditing
|
415
|
+
yield
|
416
|
+
ensure
|
417
|
+
disable_auditing unless auditing_was_enabled
|
418
|
+
end
|
419
|
+
|
341
420
|
def disable_auditing
|
342
421
|
self.auditing_enabled = false
|
343
422
|
end
|
@@ -355,7 +434,7 @@ module Audited
|
|
355
434
|
end
|
356
435
|
|
357
436
|
def auditing_enabled
|
358
|
-
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
437
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true) && Audited.auditing_enabled
|
359
438
|
end
|
360
439
|
|
361
440
|
def auditing_enabled=(val)
|