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.

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -9
  3. data/Appraisals +10 -6
  4. data/Gemfile +1 -13
  5. data/README.md +46 -33
  6. data/Rakefile +3 -18
  7. data/gemfiles/rails40.gemfile +1 -5
  8. data/gemfiles/rails41.gemfile +1 -5
  9. data/gemfiles/rails42.gemfile +1 -5
  10. data/gemfiles/rails50.gemfile +8 -0
  11. data/lib/audited-rspec.rb +4 -0
  12. data/lib/audited.rb +15 -2
  13. data/lib/audited/audit.rb +97 -57
  14. data/lib/audited/auditor.rb +73 -45
  15. data/lib/audited/rspec_matchers.rb +6 -2
  16. data/lib/audited/sweeper.rb +12 -23
  17. data/lib/audited/version.rb +1 -1
  18. data/lib/generators/audited/install_generator.rb +20 -0
  19. data/lib/generators/audited/migration.rb +15 -0
  20. data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
  21. data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
  22. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
  23. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
  24. data/lib/generators/audited/templates/install.rb +30 -0
  25. data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
  26. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
  27. data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
  28. data/lib/generators/audited/upgrade_generator.rb +57 -0
  29. data/spec/audited/audit_spec.rb +199 -0
  30. data/spec/audited/auditor_spec.rb +607 -0
  31. data/spec/audited/sweeper_spec.rb +106 -0
  32. data/spec/audited_spec_helpers.rb +6 -22
  33. data/spec/rails_app/config/environments/test.rb +7 -4
  34. data/spec/rails_app/config/initializers/secret_token.rb +1 -1
  35. data/spec/rails_app/config/routes.rb +1 -4
  36. data/spec/spec_helper.rb +7 -9
  37. data/spec/support/active_record/models.rb +20 -13
  38. data/spec/support/active_record/schema.rb +36 -12
  39. data/test/db/version_1.rb +4 -4
  40. data/test/db/version_2.rb +4 -4
  41. data/test/db/version_3.rb +4 -4
  42. data/test/db/version_4.rb +4 -4
  43. data/test/db/version_5.rb +2 -2
  44. data/test/db/version_6.rb +2 -2
  45. data/test/install_generator_test.rb +1 -1
  46. data/test/upgrade_generator_test.rb +10 -10
  47. metadata +73 -37
  48. data/lib/audited/active_record/version.rb +0 -5
  49. data/lib/audited/mongo_mapper/version.rb +0 -5
  50. data/spec/support/mongo_mapper/connection.rb +0 -4
  51. data/spec/support/mongo_mapper/models.rb +0 -214
@@ -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::Adapters::ActiveRecord::Auditor::ClassMethods#audited</tt>
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 :except => :password
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 self.included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
39
+ return if included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
40
40
 
41
- class_attribute :non_audited_columns, :instance_writer => false
42
- class_attribute :audit_associated_with, :instance_writer => false
41
+ class_attribute :audit_associated_with, instance_writer: false
42
+ class_attribute :audited_options, instance_writer: false
43
43
 
44
- if options[:only]
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, :if => :auditing_enabled
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, :as => :auditable, :class_name => Audited.audit_class.name
61
- Audited.audit_class.audited_class_names << self.to_s
54
+ has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audit.name
55
+ Audit.audited_class_names << to_s
62
56
 
63
- after_create :audit_create if !options[:on] || (options[:on] && options[:on].include?(:create))
64
- before_update :audit_update if !options[:on] || (options[:on] && options[:on].include?(:update))
65
- before_destroy :audit_destroy if !options[:on] || (options[:on] && options[:on].include?(:destroy))
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, :if => lambda { self.respond_to?(:after_audit) }
72
- set_callback :audit, :around, :around_audit, :if => lambda { self.respond_to?(: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, :as => :associated, :class_name => Audited.audit_class.name
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 Audited.audit_class.reconstruct_attributes(audits_to(version))
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 Audited.audit_class.reconstruct_attributes(audits) unless audits.empty?
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
- self.dup.tap do |revision|
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', self.destroyed?
143
- revision.send :instance_variable_set, '@persisted', !self.destroyed?
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
- Audited.audit_class.assign_revision_attributes(revision, attributes)
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
- for ivar in revision.instance_variables
162
+ revision.instance_variables.each do |ivar|
156
163
  proxy = revision.instance_variable_get ivar
157
- if !proxy.nil? and proxy.respond_to? :proxy_respond_to?
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
- changed_attributes.except(*non_audited_columns).inject({}) do |changes,(attr, old_value)|
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(:action => 'create', :audited_changes => audited_attributes,
191
- :comment => audit_comment)
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(:action => 'update', :audited_changes => changes,
197
- :comment => audit_comment)
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(:action => 'destroy', :audited_changes => audited_attributes,
203
- :comment => audit_comment) unless self.new_record?
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] = self.send(audit_associated_with) unless audit_associated_with.nil?
222
+ attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil?
208
223
  self.audit_comment = nil
209
- run_callbacks(:audit) { self.audits.create(attrs) } if auditing_enabled
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= val
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
- self.columns.select { |c| !non_audited_columns.include?(c.name) }
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( user, &block )
269
- Audited.audit_class.as_user( user, &block )
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= val
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
- (!reflection.nil?) &&
171
+ !reflection.nil? &&
168
172
  reflection.macro == :has_many &&
169
- reflection.options[:class_name] == Audited.audit_class.name
173
+ reflection.options[:class_name] == Audit.name
170
174
  end
171
175
  end
172
176
  end
@@ -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.audit_class
6
+ observe Audited::Audit
7
7
 
8
- attr_accessor :controller
9
- def before(controller)
8
+ def around(controller)
10
9
  self.controller = controller
11
- true
12
- end
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
- if defined?(::ActiveRecord)
34
- super
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 = :"_notify_audited_sweeper"
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
- if defined?(ActionController) and defined?(ActionController::Base)
59
- # Create dynamic subclass of Audited::Sweeper otherwise rspec will
60
- # fail with both ActiveRecord and MongoMapper tests as there will be
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
- ActionController::Base.class_eval do
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
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.2.2"
2
+ VERSION = "4.3.0"
3
3
  end
@@ -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,9 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :comment, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :audits, :comment
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :remote_address, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :audits, :remote_address
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :request_uuid, :string
4
+ add_index :audits, :request_uuid
5
+ end
6
+
7
+ def self.down
8
+ remove_column :audits, :request_uuid
9
+ end
10
+ 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