audited 4.2.1 → 4.4.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +20 -9
- data/Appraisals +11 -6
- data/CHANGELOG.md +190 -0
- data/Gemfile +1 -13
- data/README.md +65 -34
- data/Rakefile +3 -18
- data/gemfiles/rails40.gemfile +1 -5
- data/gemfiles/rails41.gemfile +1 -5
- data/gemfiles/rails42.gemfile +1 -5
- data/gemfiles/rails50.gemfile +7 -0
- data/gemfiles/rails51.gemfile +7 -0
- data/lib/audited/audit.rb +126 -56
- data/lib/audited/auditor.rb +76 -44
- data/lib/audited/rspec_matchers.rb +5 -1
- data/lib/audited/sweeper.rb +24 -46
- data/lib/audited/version.rb +1 -1
- data/lib/audited-rspec.rb +4 -0
- data/lib/audited.rb +16 -2
- data/lib/generators/audited/install_generator.rb +24 -0
- data/lib/generators/audited/migration.rb +15 -0
- data/lib/generators/audited/migration_helper.rb +9 -0
- data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
- data/lib/generators/audited/templates/install.rb +30 -0
- data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
- data/lib/generators/audited/upgrade_generator.rb +59 -0
- data/spec/audited/audit_spec.rb +246 -0
- data/spec/audited/auditor_spec.rb +648 -0
- data/spec/audited/sweeper_spec.rb +108 -0
- data/spec/audited_spec_helpers.rb +6 -22
- data/spec/rails_app/config/environments/test.rb +7 -4
- data/spec/rails_app/config/initializers/secret_token.rb +1 -1
- data/spec/rails_app/config/routes.rb +1 -4
- data/spec/spec_helper.rb +7 -9
- data/spec/support/active_record/models.rb +24 -13
- data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +12 -0
- data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +12 -0
- data/spec/support/active_record/schema.rb +37 -12
- data/test/db/version_1.rb +4 -4
- data/test/db/version_2.rb +4 -4
- data/test/db/version_3.rb +4 -4
- data/test/db/version_4.rb +4 -4
- data/test/db/version_5.rb +2 -2
- data/test/db/version_6.rb +2 -2
- data/test/install_generator_test.rb +31 -3
- data/test/upgrade_generator_test.rb +25 -10
- metadata +69 -43
- data/CHANGELOG +0 -34
- data/lib/audited/active_record/version.rb +0 -5
- data/lib/audited/mongo_mapper/version.rb +0 -5
- data/spec/support/mongo_mapper/connection.rb +0 -4
- data/spec/support/mongo_mapper/models.rb +0 -214
data/lib/audited/audit.rb
CHANGED
|
@@ -1,60 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
module Audit
|
|
3
|
-
def self.included(klass)
|
|
4
|
-
klass.extend(ClassMethods)
|
|
5
|
-
klass.setup_audit
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
module ClassMethods
|
|
9
|
-
def setup_audit
|
|
10
|
-
belongs_to :auditable, :polymorphic => true
|
|
11
|
-
belongs_to :user, :polymorphic => true
|
|
12
|
-
belongs_to :associated, :polymorphic => true
|
|
1
|
+
require 'set'
|
|
13
2
|
|
|
14
|
-
|
|
3
|
+
module Audited
|
|
4
|
+
# Audit saves the changes to ActiveRecord models. It has the following attributes:
|
|
5
|
+
#
|
|
6
|
+
# * <tt>auditable</tt>: the ActiveRecord model that was changed
|
|
7
|
+
# * <tt>user</tt>: the user that performed the change; a string or an ActiveRecord model
|
|
8
|
+
# * <tt>action</tt>: one of create, update, or delete
|
|
9
|
+
# * <tt>audited_changes</tt>: a hash of all the changes
|
|
10
|
+
# * <tt>comment</tt>: a comment set with the audit
|
|
11
|
+
# * <tt>version</tt>: the version of the model
|
|
12
|
+
# * <tt>request_uuid</tt>: a uuid based that allows audits from the same controller request
|
|
13
|
+
# * <tt>created_at</tt>: Time that the change was performed
|
|
14
|
+
#
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
class YAMLIfTextColumnType
|
|
17
|
+
class << self
|
|
18
|
+
def load(obj)
|
|
19
|
+
if Audited.audit_class.columns_hash["audited_changes"].sql_type == "text"
|
|
20
|
+
ActiveRecord::Coders::YAMLColumn.new(Object).load(obj)
|
|
21
|
+
else
|
|
22
|
+
obj
|
|
23
|
+
end
|
|
18
24
|
end
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
def dump(obj)
|
|
27
|
+
if Audited.audit_class.columns_hash["audited_changes"].sql_type == "text"
|
|
28
|
+
ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj)
|
|
29
|
+
else
|
|
30
|
+
obj
|
|
31
|
+
end
|
|
23
32
|
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Thread.current[:audited_user] = user
|
|
30
|
-
yield
|
|
31
|
-
ensure
|
|
32
|
-
Thread.current[:audited_user] = nil
|
|
33
|
-
end
|
|
36
|
+
class Audit < ::ActiveRecord::Base
|
|
37
|
+
belongs_to :auditable, polymorphic: true
|
|
38
|
+
belongs_to :user, polymorphic: true
|
|
39
|
+
belongs_to :associated, polymorphic: true
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
def reconstruct_attributes(audits)
|
|
37
|
-
attributes = {}
|
|
38
|
-
result = audits.collect do |audit|
|
|
39
|
-
attributes.merge!(audit.new_attributes).merge!(:version => audit.version)
|
|
40
|
-
yield attributes if block_given?
|
|
41
|
-
end
|
|
42
|
-
block_given? ? result : attributes
|
|
43
|
-
end
|
|
41
|
+
before_create :set_version_number, :set_audit_user, :set_request_uuid, :set_remote_address
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
attributes.each do |attr, val|
|
|
48
|
-
record = record.dup if record.frozen?
|
|
43
|
+
cattr_accessor :audited_class_names
|
|
44
|
+
self.audited_class_names = Set.new
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
serialize :audited_changes, YAMLIfTextColumnType
|
|
47
|
+
|
|
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')}
|
|
53
|
+
|
|
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)}
|
|
58
|
+
# Return all audits older than the current one.
|
|
59
|
+
def ancestors
|
|
60
|
+
self.class.ascending.auditable_finder(auditable_id, auditable_type).to_version(version)
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
# Return an instance of what the object looked like at this revision. If
|
|
@@ -62,13 +65,13 @@ module Audited
|
|
|
62
65
|
def revision
|
|
63
66
|
clazz = auditable_type.constantize
|
|
64
67
|
(clazz.find_by_id(auditable_id) || clazz.new).tap do |m|
|
|
65
|
-
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(
|
|
68
|
+
self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(version: version))
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
# Returns a hash of the changed attributes with the new values
|
|
70
73
|
def new_attributes
|
|
71
|
-
(audited_changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
|
|
74
|
+
(audited_changes || {}).inject({}.with_indifferent_access) do |attrs, (attr, values)|
|
|
72
75
|
attrs[attr] = values.is_a?(Array) ? values.last : values
|
|
73
76
|
attrs
|
|
74
77
|
end
|
|
@@ -76,29 +79,96 @@ module Audited
|
|
|
76
79
|
|
|
77
80
|
# Returns a hash of the changed attributes with the old values
|
|
78
81
|
def old_attributes
|
|
79
|
-
(audited_changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
|
|
82
|
+
(audited_changes || {}).inject({}.with_indifferent_access) do |attrs, (attr, values)|
|
|
80
83
|
attrs[attr] = Array(values).first
|
|
81
84
|
|
|
82
85
|
attrs
|
|
83
86
|
end
|
|
84
87
|
end
|
|
85
88
|
|
|
89
|
+
# Allows user to be set to either a string or an ActiveRecord object
|
|
90
|
+
# @private
|
|
91
|
+
def user_as_string=(user)
|
|
92
|
+
# reset both either way
|
|
93
|
+
self.user_as_model = self.username = nil
|
|
94
|
+
user.is_a?(::ActiveRecord::Base) ?
|
|
95
|
+
self.user_as_model = user :
|
|
96
|
+
self.username = user
|
|
97
|
+
end
|
|
98
|
+
alias_method :user_as_model=, :user=
|
|
99
|
+
alias_method :user=, :user_as_string=
|
|
100
|
+
|
|
101
|
+
# @private
|
|
102
|
+
def user_as_string
|
|
103
|
+
user_as_model || username
|
|
104
|
+
end
|
|
105
|
+
alias_method :user_as_model, :user
|
|
106
|
+
alias_method :user, :user_as_string
|
|
107
|
+
|
|
108
|
+
# Returns the list of classes that are being audited
|
|
109
|
+
def self.audited_classes
|
|
110
|
+
audited_class_names.map(&:constantize)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# All audits made during the block called will be recorded as made
|
|
114
|
+
# by +user+. This method is hopefully threadsafe, making it ideal
|
|
115
|
+
# for background operations that require audit information.
|
|
116
|
+
def self.as_user(user, &block)
|
|
117
|
+
::Audited.store[:audited_user] = user
|
|
118
|
+
yield
|
|
119
|
+
ensure
|
|
120
|
+
::Audited.store[:audited_user] = nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @private
|
|
124
|
+
def self.reconstruct_attributes(audits)
|
|
125
|
+
attributes = {}
|
|
126
|
+
result = audits.collect do |audit|
|
|
127
|
+
attributes.merge!(audit.new_attributes)[:version] = audit.version
|
|
128
|
+
yield attributes if block_given?
|
|
129
|
+
end
|
|
130
|
+
block_given? ? result : attributes
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @private
|
|
134
|
+
def self.assign_revision_attributes(record, attributes)
|
|
135
|
+
attributes.each do |attr, val|
|
|
136
|
+
record = record.dup if record.frozen?
|
|
137
|
+
|
|
138
|
+
if record.respond_to?("#{attr}=")
|
|
139
|
+
record.attributes.key?(attr.to_s) ?
|
|
140
|
+
record[attr] = val :
|
|
141
|
+
record.send("#{attr}=", val)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
record
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# use created_at as timestamp cache key
|
|
148
|
+
def self.collection_cache_key(collection = all, timestamp_column = :created_at)
|
|
149
|
+
super(collection, :created_at)
|
|
150
|
+
end
|
|
151
|
+
|
|
86
152
|
private
|
|
153
|
+
|
|
87
154
|
def set_version_number
|
|
88
|
-
max = self.class.
|
|
89
|
-
:auditable_id => auditable_id,
|
|
90
|
-
:auditable_type => auditable_type
|
|
91
|
-
).order(:version.desc).first.try(:version) || 0
|
|
155
|
+
max = self.class.auditable_finder(auditable_id, auditable_type).descending.first.try(:version) || 0
|
|
92
156
|
self.version = max + 1
|
|
93
157
|
end
|
|
94
158
|
|
|
95
159
|
def set_audit_user
|
|
96
|
-
self.user
|
|
160
|
+
self.user ||= ::Audited.store[:audited_user] # from .as_user
|
|
161
|
+
self.user ||= ::Audited.store[:current_user] # from Sweeper
|
|
97
162
|
nil # prevent stopping callback chains
|
|
98
163
|
end
|
|
99
164
|
|
|
100
165
|
def set_request_uuid
|
|
166
|
+
self.request_uuid ||= ::Audited.store[:current_request_uuid]
|
|
101
167
|
self.request_uuid ||= SecureRandom.uuid
|
|
102
168
|
end
|
|
169
|
+
|
|
170
|
+
def set_remote_address
|
|
171
|
+
self.remote_address ||= ::Audited.store[:current_remote_address]
|
|
172
|
+
end
|
|
103
173
|
end
|
|
104
174
|
end
|
data/lib/audited/auditor.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Audited
|
|
|
9
9
|
# To store an audit comment set model.audit_comment to your comment before
|
|
10
10
|
# a create, update or destroy operation.
|
|
11
11
|
#
|
|
12
|
-
# See <tt>Audited::
|
|
12
|
+
# See <tt>Audited::Auditor::ClassMethods#audited</tt>
|
|
13
13
|
# for configuration options
|
|
14
14
|
module Auditor #:nodoc:
|
|
15
15
|
extend ActiveSupport::Concern
|
|
@@ -28,7 +28,7 @@ module Audited
|
|
|
28
28
|
# You can add to those by passing one or an array of fields to skip.
|
|
29
29
|
#
|
|
30
30
|
# class User < ActiveRecord::Base
|
|
31
|
-
# audited :
|
|
31
|
+
# audited except: :password
|
|
32
32
|
# end
|
|
33
33
|
#
|
|
34
34
|
# * +require_comment+ - Ensures that audit_comment is supplied before
|
|
@@ -36,40 +36,35 @@ module Audited
|
|
|
36
36
|
#
|
|
37
37
|
def audited(options = {})
|
|
38
38
|
# don't allow multiple calls
|
|
39
|
-
return if
|
|
39
|
+
return if included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
|
|
40
40
|
|
|
41
|
-
class_attribute :
|
|
42
|
-
class_attribute :
|
|
41
|
+
class_attribute :audit_associated_with, instance_writer: false
|
|
42
|
+
class_attribute :audited_options, instance_writer: false
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
except = self.column_names - Array(options[:only]).flatten.map(&:to_s)
|
|
46
|
-
else
|
|
47
|
-
except = default_ignored_attributes + Audited.ignored_attributes
|
|
48
|
-
except |= Array(options[:except]).collect(&:to_s) if options[:except]
|
|
49
|
-
end
|
|
50
|
-
self.non_audited_columns = except
|
|
44
|
+
self.audited_options = options
|
|
51
45
|
self.audit_associated_with = options[:associated_with]
|
|
52
46
|
|
|
53
47
|
if options[:comment_required]
|
|
54
|
-
validates_presence_of :audit_comment, :
|
|
48
|
+
validates_presence_of :audit_comment, if: :auditing_enabled
|
|
55
49
|
before_destroy :require_comment
|
|
56
50
|
end
|
|
57
51
|
|
|
58
52
|
attr_accessor :audit_comment
|
|
59
53
|
|
|
60
|
-
has_many :audits, :
|
|
61
|
-
Audited.audit_class.audited_class_names <<
|
|
54
|
+
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name
|
|
55
|
+
Audited.audit_class.audited_class_names << to_s
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
on = Array(options[:on])
|
|
58
|
+
after_create :audit_create if on.empty? || on.include?(:create)
|
|
59
|
+
before_update :audit_update if on.empty? || on.include?(:update)
|
|
60
|
+
before_destroy :audit_destroy if on.empty? || on.include?(:destroy)
|
|
66
61
|
|
|
67
62
|
# Define and set after_audit and around_audit callbacks. This might be useful if you want
|
|
68
63
|
# to notify a party after the audit has been created or if you want to access the newly-created
|
|
69
64
|
# audit.
|
|
70
65
|
define_callbacks :audit
|
|
71
|
-
set_callback :audit, :after, :after_audit, :
|
|
72
|
-
set_callback :audit, :around, :around_audit, :
|
|
66
|
+
set_callback :audit, :after, :after_audit, if: lambda { respond_to?(:after_audit, true) }
|
|
67
|
+
set_callback :audit, :around, :around_audit, if: lambda { respond_to?(:around_audit, true) }
|
|
73
68
|
|
|
74
69
|
attr_accessor :version
|
|
75
70
|
|
|
@@ -80,7 +75,11 @@ module Audited
|
|
|
80
75
|
end
|
|
81
76
|
|
|
82
77
|
def has_associated_audits
|
|
83
|
-
has_many :associated_audits, :
|
|
78
|
+
has_many :associated_audits, as: :associated, class_name: Audited.audit_class.name
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_ignored_attributes
|
|
82
|
+
[primary_key, inheritance_column]
|
|
84
83
|
end
|
|
85
84
|
end
|
|
86
85
|
|
|
@@ -133,14 +132,22 @@ module Audited
|
|
|
133
132
|
attributes.except(*non_audited_columns)
|
|
134
133
|
end
|
|
135
134
|
|
|
135
|
+
def non_audited_columns
|
|
136
|
+
self.class.non_audited_columns
|
|
137
|
+
end
|
|
138
|
+
|
|
136
139
|
protected
|
|
137
140
|
|
|
141
|
+
def non_audited_columns
|
|
142
|
+
self.class.non_audited_columns
|
|
143
|
+
end
|
|
144
|
+
|
|
138
145
|
def revision_with(attributes)
|
|
139
|
-
|
|
146
|
+
dup.tap do |revision|
|
|
140
147
|
revision.id = id
|
|
141
148
|
revision.send :instance_variable_set, '@attributes', self.attributes if rails_below?('4.2.0')
|
|
142
|
-
revision.send :instance_variable_set, '@new_record',
|
|
143
|
-
revision.send :instance_variable_set, '@persisted', !
|
|
149
|
+
revision.send :instance_variable_set, '@new_record', destroyed?
|
|
150
|
+
revision.send :instance_variable_set, '@persisted', !destroyed?
|
|
144
151
|
revision.send :instance_variable_set, '@readonly', false
|
|
145
152
|
revision.send :instance_variable_set, '@destroyed', false
|
|
146
153
|
revision.send :instance_variable_set, '@_destroyed', false
|
|
@@ -152,9 +159,9 @@ module Audited
|
|
|
152
159
|
# to determine if an instance variable is a proxy object is to
|
|
153
160
|
# see if it responds to certain methods, as it forwards almost
|
|
154
161
|
# everything to its target.
|
|
155
|
-
|
|
162
|
+
revision.instance_variables.each do |ivar|
|
|
156
163
|
proxy = revision.instance_variable_get ivar
|
|
157
|
-
if !proxy.nil?
|
|
164
|
+
if !proxy.nil? && proxy.respond_to?(:proxy_respond_to?)
|
|
158
165
|
revision.instance_variable_set ivar, nil
|
|
159
166
|
end
|
|
160
167
|
end
|
|
@@ -168,7 +175,15 @@ module Audited
|
|
|
168
175
|
private
|
|
169
176
|
|
|
170
177
|
def audited_changes
|
|
171
|
-
|
|
178
|
+
collection =
|
|
179
|
+
if audited_options[:only]
|
|
180
|
+
audited_columns = self.class.audited_columns.map(&:name)
|
|
181
|
+
changed_attributes.slice(*audited_columns)
|
|
182
|
+
else
|
|
183
|
+
changed_attributes.except(*non_audited_columns)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
collection.inject({}) do |changes, (attr, old_value)|
|
|
172
187
|
changes[attr] = [old_value, self[attr]]
|
|
173
188
|
changes
|
|
174
189
|
end
|
|
@@ -187,32 +202,33 @@ module Audited
|
|
|
187
202
|
end
|
|
188
203
|
|
|
189
204
|
def audit_create
|
|
190
|
-
write_audit(:
|
|
191
|
-
:
|
|
205
|
+
write_audit(action: 'create', audited_changes: audited_attributes,
|
|
206
|
+
comment: audit_comment)
|
|
192
207
|
end
|
|
193
208
|
|
|
194
209
|
def audit_update
|
|
195
210
|
unless (changes = audited_changes).empty? && audit_comment.blank?
|
|
196
|
-
write_audit(:
|
|
197
|
-
:
|
|
211
|
+
write_audit(action: 'update', audited_changes: changes,
|
|
212
|
+
comment: audit_comment)
|
|
198
213
|
end
|
|
199
214
|
end
|
|
200
215
|
|
|
201
216
|
def audit_destroy
|
|
202
|
-
write_audit(:
|
|
203
|
-
:
|
|
217
|
+
write_audit(action: 'destroy', audited_changes: audited_attributes,
|
|
218
|
+
comment: audit_comment) unless new_record?
|
|
204
219
|
end
|
|
205
220
|
|
|
206
221
|
def write_audit(attrs)
|
|
207
|
-
attrs[:associated] =
|
|
222
|
+
attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil?
|
|
208
223
|
self.audit_comment = nil
|
|
209
|
-
run_callbacks(:audit) {
|
|
224
|
+
run_callbacks(:audit) { audits.create(attrs) } if auditing_enabled
|
|
210
225
|
end
|
|
211
226
|
|
|
212
227
|
def require_comment
|
|
213
228
|
if auditing_enabled && audit_comment.blank?
|
|
214
229
|
errors.add(:audit_comment, "Comment required before destruction")
|
|
215
|
-
return false
|
|
230
|
+
return false if Rails.version.start_with?('4.')
|
|
231
|
+
throw :abort
|
|
216
232
|
end
|
|
217
233
|
end
|
|
218
234
|
|
|
@@ -227,16 +243,32 @@ module Audited
|
|
|
227
243
|
self.class.auditing_enabled
|
|
228
244
|
end
|
|
229
245
|
|
|
230
|
-
def auditing_enabled=
|
|
246
|
+
def auditing_enabled=(val)
|
|
231
247
|
self.class.auditing_enabled = val
|
|
232
248
|
end
|
|
233
|
-
|
|
234
249
|
end # InstanceMethods
|
|
235
250
|
|
|
236
251
|
module AuditedClassMethods
|
|
237
252
|
# Returns an array of columns that are audited. See non_audited_columns
|
|
238
253
|
def audited_columns
|
|
239
|
-
|
|
254
|
+
columns.select {|c| !non_audited_columns.include?(c.name) }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def non_audited_columns
|
|
258
|
+
@non_audited_columns ||= begin
|
|
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
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def non_audited_columns=(columns)
|
|
271
|
+
@non_audited_columns = columns
|
|
240
272
|
end
|
|
241
273
|
|
|
242
274
|
# Executes the block with auditing disabled.
|
|
@@ -265,16 +297,16 @@ module Audited
|
|
|
265
297
|
# made by +user+. This is not model specific, the method is a
|
|
266
298
|
# convenience wrapper around
|
|
267
299
|
# @see Audit#as_user.
|
|
268
|
-
def audit_as(
|
|
269
|
-
Audited.audit_class.as_user(
|
|
300
|
+
def audit_as(user, &block)
|
|
301
|
+
Audited.audit_class.as_user(user, &block)
|
|
270
302
|
end
|
|
271
303
|
|
|
272
304
|
def auditing_enabled
|
|
273
|
-
Audited.store.fetch("#{
|
|
305
|
+
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
|
274
306
|
end
|
|
275
307
|
|
|
276
|
-
def auditing_enabled=
|
|
277
|
-
Audited.store["#{
|
|
308
|
+
def auditing_enabled=(val)
|
|
309
|
+
Audited.store["#{table_name}_auditing_enabled"] = val
|
|
278
310
|
end
|
|
279
311
|
end
|
|
280
312
|
end
|
|
@@ -76,6 +76,8 @@ module Audited
|
|
|
76
76
|
"Did not expect #{@expectation}"
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
alias_method :failure_message_when_negated, :negative_failure_message
|
|
80
|
+
|
|
79
81
|
def description
|
|
80
82
|
description = "audited"
|
|
81
83
|
description += " associated with #{@options[:associated_with]}" if @options.key?(:associated_with)
|
|
@@ -149,6 +151,8 @@ module Audited
|
|
|
149
151
|
"Expected #{model_class} to not have associated audits"
|
|
150
152
|
end
|
|
151
153
|
|
|
154
|
+
alias_method :failure_message_when_negated, :negative_failure_message
|
|
155
|
+
|
|
152
156
|
def description
|
|
153
157
|
"has associated audits"
|
|
154
158
|
end
|
|
@@ -164,7 +168,7 @@ module Audited
|
|
|
164
168
|
end
|
|
165
169
|
|
|
166
170
|
def association_exists?
|
|
167
|
-
|
|
171
|
+
!reflection.nil? &&
|
|
168
172
|
reflection.macro == :has_many &&
|
|
169
173
|
reflection.options[:class_name] == Audited.audit_class.name
|
|
170
174
|
end
|
data/lib/audited/sweeper.rb
CHANGED
|
@@ -1,71 +1,49 @@
|
|
|
1
|
-
require "rails/observers/activerecord/active_record"
|
|
2
|
-
require "rails/observers/action_controller/caching"
|
|
3
|
-
|
|
4
1
|
module Audited
|
|
5
|
-
class Sweeper
|
|
6
|
-
|
|
2
|
+
class Sweeper
|
|
3
|
+
STORED_DATA = {
|
|
4
|
+
current_remote_address: :remote_ip,
|
|
5
|
+
current_request_uuid: :request_uuid,
|
|
6
|
+
current_user: :current_user
|
|
7
|
+
}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
def before(controller)
|
|
10
|
-
self.controller = controller
|
|
11
|
-
true
|
|
12
|
-
end
|
|
9
|
+
delegate :store, to: ::Audited
|
|
13
10
|
|
|
14
|
-
def
|
|
11
|
+
def around(controller)
|
|
12
|
+
self.controller = controller
|
|
13
|
+
STORED_DATA.each { |k,m| store[k] = send(m) }
|
|
14
|
+
yield
|
|
15
|
+
ensure
|
|
15
16
|
self.controller = nil
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def before_create(audit)
|
|
19
|
-
audit.user ||= current_user
|
|
20
|
-
audit.remote_address = controller.try(:request).try(:remote_ip)
|
|
21
|
-
audit.request_uuid = request_uuid if request_uuid
|
|
17
|
+
STORED_DATA.keys.each { |k| store.delete(k) }
|
|
22
18
|
end
|
|
23
19
|
|
|
24
20
|
def current_user
|
|
25
21
|
controller.send(Audited.current_user_method) if controller.respond_to?(Audited.current_user_method, true)
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
def
|
|
29
|
-
controller.try(:request).try(:
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def add_observer!(klass)
|
|
33
|
-
if defined?(::ActiveRecord)
|
|
34
|
-
super
|
|
35
|
-
define_callback(klass)
|
|
36
|
-
end
|
|
24
|
+
def remote_ip
|
|
25
|
+
controller.try(:request).try(:remote_ip)
|
|
37
26
|
end
|
|
38
27
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
callback_meth = :"_notify_audited_sweeper"
|
|
42
|
-
klass.send(:define_method, callback_meth) do
|
|
43
|
-
observer.update(:before_create, self)
|
|
44
|
-
end
|
|
45
|
-
klass.send(:before_create, callback_meth)
|
|
28
|
+
def request_uuid
|
|
29
|
+
controller.try(:request).try(:uuid)
|
|
46
30
|
end
|
|
47
31
|
|
|
48
32
|
def controller
|
|
49
|
-
|
|
33
|
+
store[:current_controller]
|
|
50
34
|
end
|
|
51
35
|
|
|
52
36
|
def controller=(value)
|
|
53
|
-
|
|
37
|
+
store[:current_controller] = value
|
|
54
38
|
end
|
|
55
39
|
end
|
|
56
40
|
end
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# around_filter collision
|
|
62
|
-
sweeper_class = Class.new(Audited::Sweeper) do
|
|
63
|
-
def self.name
|
|
64
|
-
"#{Audited.audit_class}::Sweeper"
|
|
65
|
-
end
|
|
42
|
+
ActiveSupport.on_load(:action_controller) do
|
|
43
|
+
if defined?(ActionController::Base)
|
|
44
|
+
ActionController::Base.around_action Audited::Sweeper.new
|
|
66
45
|
end
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
around_filter sweeper_class.instance
|
|
46
|
+
if defined?(ActionController::API)
|
|
47
|
+
ActionController::API.around_action Audited::Sweeper.new
|
|
70
48
|
end
|
|
71
49
|
end
|
data/lib/audited/version.rb
CHANGED
data/lib/audited.rb
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
require '
|
|
2
|
-
|
|
1
|
+
require 'active_record'
|
|
3
2
|
|
|
4
3
|
module Audited
|
|
5
4
|
class << self
|
|
6
5
|
attr_accessor :ignored_attributes, :current_user_method, :audit_class
|
|
7
6
|
|
|
7
|
+
def audit_class
|
|
8
|
+
@audit_class || Audit
|
|
9
|
+
end
|
|
10
|
+
|
|
8
11
|
def store
|
|
9
12
|
Thread.current[:audited_store] ||= {}
|
|
10
13
|
end
|
|
14
|
+
|
|
15
|
+
def config
|
|
16
|
+
yield(self)
|
|
17
|
+
end
|
|
11
18
|
end
|
|
12
19
|
|
|
13
20
|
@ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
|
|
14
21
|
|
|
15
22
|
@current_user_method = :current_user
|
|
16
23
|
end
|
|
24
|
+
|
|
25
|
+
require 'audited/auditor'
|
|
26
|
+
require 'audited/audit'
|
|
27
|
+
|
|
28
|
+
::ActiveRecord::Base.send :include, Audited::Auditor
|
|
29
|
+
|
|
30
|
+
require 'audited/sweeper'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/migration'
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
require 'generators/audited/migration'
|
|
6
|
+
require 'generators/audited/migration_helper'
|
|
7
|
+
|
|
8
|
+
module Audited
|
|
9
|
+
module Generators
|
|
10
|
+
class InstallGenerator < Rails::Generators::Base
|
|
11
|
+
include Rails::Generators::Migration
|
|
12
|
+
include Audited::Generators::MigrationHelper
|
|
13
|
+
extend Audited::Generators::Migration
|
|
14
|
+
|
|
15
|
+
class_option :audited_changes_column_type, type: :string, default: "text", required: false
|
|
16
|
+
|
|
17
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
18
|
+
|
|
19
|
+
def copy_migration
|
|
20
|
+
migration_template 'install.rb', 'db/migrate/install_audited.rb'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Audited
|
|
2
|
+
module Generators
|
|
3
|
+
module Migration
|
|
4
|
+
# Implement the required interface for Rails::Generators::Migration.
|
|
5
|
+
def next_migration_number(dirname) #:nodoc:
|
|
6
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
7
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
|
8
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
|
9
|
+
else
|
|
10
|
+
"%.3d" % next_migration_number
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|