audited 4.2.2 → 4.3.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 +4 -4
- data/.travis.yml +10 -9
- data/Appraisals +10 -6
- data/Gemfile +1 -13
- data/README.md +46 -33
- 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 +8 -0
- data/lib/audited-rspec.rb +4 -0
- data/lib/audited.rb +15 -2
- data/lib/audited/audit.rb +97 -57
- data/lib/audited/auditor.rb +73 -45
- data/lib/audited/rspec_matchers.rb +6 -2
- data/lib/audited/sweeper.rb +12 -23
- data/lib/audited/version.rb +1 -1
- data/lib/generators/audited/install_generator.rb +20 -0
- data/lib/generators/audited/migration.rb +15 -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 +57 -0
- data/spec/audited/audit_spec.rb +199 -0
- data/spec/audited/auditor_spec.rb +607 -0
- data/spec/audited/sweeper_spec.rb +106 -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 +20 -13
- data/spec/support/active_record/schema.rb +36 -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 +1 -1
- data/test/upgrade_generator_test.rb +10 -10
- metadata +73 -37
- 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/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
|
-
|
54
|
+
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audit.name
|
55
|
+
Audit.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: Audit.name
|
79
|
+
end
|
80
|
+
|
81
|
+
def default_ignored_attributes
|
82
|
+
[primary_key, inheritance_column]
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
@@ -119,13 +118,13 @@ module Audited
|
|
119
118
|
|
120
119
|
# Get a specific revision specified by the version number, or +:previous+
|
121
120
|
def revision(version)
|
122
|
-
revision_with
|
121
|
+
revision_with Audit.reconstruct_attributes(audits_to(version))
|
123
122
|
end
|
124
123
|
|
125
124
|
# Find the oldest revision recorded prior to the date/time provided.
|
126
125
|
def revision_at(date_or_time)
|
127
126
|
audits = self.audits.up_until(date_or_time)
|
128
|
-
revision_with
|
127
|
+
revision_with Audit.reconstruct_attributes(audits) unless audits.empty?
|
129
128
|
end
|
130
129
|
|
131
130
|
# List of attributes that are audited.
|
@@ -133,28 +132,36 @@ 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
|
147
154
|
revision.send :instance_variable_set, '@marked_for_destruction', false
|
148
|
-
|
155
|
+
Audit.assign_revision_attributes(revision, attributes)
|
149
156
|
|
150
157
|
# Remove any association proxies so that they will be recreated
|
151
158
|
# and reference the correct object for this revision. The only way
|
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,28 @@ 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
|
240
268
|
end
|
241
269
|
|
242
270
|
# Executes the block with auditing disabled.
|
@@ -265,15 +293,15 @@ module Audited
|
|
265
293
|
# made by +user+. This is not model specific, the method is a
|
266
294
|
# convenience wrapper around
|
267
295
|
# @see Audit#as_user.
|
268
|
-
def audit_as(
|
269
|
-
|
296
|
+
def audit_as(user, &block)
|
297
|
+
Audit.as_user(user, &block)
|
270
298
|
end
|
271
299
|
|
272
300
|
def auditing_enabled
|
273
301
|
Audited.store.fetch("#{table_name}_auditing_enabled", true)
|
274
302
|
end
|
275
303
|
|
276
|
-
def auditing_enabled=
|
304
|
+
def auditing_enabled=(val)
|
277
305
|
Audited.store["#{table_name}_auditing_enabled"] = val
|
278
306
|
end
|
279
307
|
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,9 +168,9 @@ module Audited
|
|
164
168
|
end
|
165
169
|
|
166
170
|
def association_exists?
|
167
|
-
|
171
|
+
!reflection.nil? &&
|
168
172
|
reflection.macro == :has_many &&
|
169
|
-
reflection.options[:class_name] ==
|
173
|
+
reflection.options[:class_name] == Audit.name
|
170
174
|
end
|
171
175
|
end
|
172
176
|
end
|
data/lib/audited/sweeper.rb
CHANGED
@@ -3,15 +3,12 @@ require "rails/observers/action_controller/caching"
|
|
3
3
|
|
4
4
|
module Audited
|
5
5
|
class Sweeper < ActionController::Caching::Sweeper
|
6
|
-
observe Audited
|
6
|
+
observe Audited::Audit
|
7
7
|
|
8
|
-
|
9
|
-
def before(controller)
|
8
|
+
def around(controller)
|
10
9
|
self.controller = controller
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def after(controller)
|
10
|
+
yield
|
11
|
+
ensure
|
15
12
|
self.controller = nil
|
16
13
|
end
|
17
14
|
|
@@ -30,15 +27,13 @@ module Audited
|
|
30
27
|
end
|
31
28
|
|
32
29
|
def add_observer!(klass)
|
33
|
-
|
34
|
-
|
35
|
-
define_callback(klass)
|
36
|
-
end
|
30
|
+
super
|
31
|
+
define_callback(klass)
|
37
32
|
end
|
38
33
|
|
39
34
|
def define_callback(klass)
|
40
35
|
observer = self
|
41
|
-
callback_meth = :
|
36
|
+
callback_meth = :_notify_audited_sweeper
|
42
37
|
klass.send(:define_method, callback_meth) do
|
43
38
|
observer.update(:before_create, self)
|
44
39
|
end
|
@@ -55,17 +50,11 @@ module Audited
|
|
55
50
|
end
|
56
51
|
end
|
57
52
|
|
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
|
53
|
+
ActiveSupport.on_load(:action_controller) do
|
54
|
+
if defined?(ActionController::Base)
|
55
|
+
ActionController::Base.around_action Audited::Sweeper.instance
|
66
56
|
end
|
67
|
-
|
68
|
-
|
69
|
-
around_filter sweeper_class.instance
|
57
|
+
if defined?(ActionController::API)
|
58
|
+
ActionController::API.around_action Audited::Sweeper.instance
|
70
59
|
end
|
71
60
|
end
|
data/lib/audited/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
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
|
+
|
7
|
+
module Audited
|
8
|
+
module Generators
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
extend Audited::Generators::Migration
|
12
|
+
|
13
|
+
source_root File.expand_path("../templates", __FILE__)
|
14
|
+
|
15
|
+
def copy_migration
|
16
|
+
migration_template 'install.rb', 'db/migrate/install_audited.rb'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
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
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
add_column :audits, :association_id, :integer
|
4
|
+
add_column :audits, :association_type, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_column :audits, :association_type
|
9
|
+
remove_column :audits, :association_id
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :audits, :force => true do |t|
|
4
|
+
t.column :auditable_id, :integer
|
5
|
+
t.column :auditable_type, :string
|
6
|
+
t.column :associated_id, :integer
|
7
|
+
t.column :associated_type, :string
|
8
|
+
t.column :user_id, :integer
|
9
|
+
t.column :user_type, :string
|
10
|
+
t.column :username, :string
|
11
|
+
t.column :action, :string
|
12
|
+
t.column :audited_changes, :text
|
13
|
+
t.column :version, :integer, :default => 0
|
14
|
+
t.column :comment, :string
|
15
|
+
t.column :remote_address, :string
|
16
|
+
t.column :request_uuid, :string
|
17
|
+
t.column :created_at, :datetime
|
18
|
+
end
|
19
|
+
|
20
|
+
add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
|
21
|
+
add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
22
|
+
add_index :audits, [:user_id, :user_type], :name => 'user_index'
|
23
|
+
add_index :audits, :request_uuid
|
24
|
+
add_index :audits, :created_at
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.down
|
28
|
+
drop_table :audits
|
29
|
+
end
|
30
|
+
end
|