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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +20 -9
  3. data/Appraisals +11 -6
  4. data/CHANGELOG.md +190 -0
  5. data/Gemfile +1 -13
  6. data/README.md +65 -34
  7. data/Rakefile +3 -18
  8. data/gemfiles/rails40.gemfile +1 -5
  9. data/gemfiles/rails41.gemfile +1 -5
  10. data/gemfiles/rails42.gemfile +1 -5
  11. data/gemfiles/rails50.gemfile +7 -0
  12. data/gemfiles/rails51.gemfile +7 -0
  13. data/lib/audited/audit.rb +126 -56
  14. data/lib/audited/auditor.rb +76 -44
  15. data/lib/audited/rspec_matchers.rb +5 -1
  16. data/lib/audited/sweeper.rb +24 -46
  17. data/lib/audited/version.rb +1 -1
  18. data/lib/audited-rspec.rb +4 -0
  19. data/lib/audited.rb +16 -2
  20. data/lib/generators/audited/install_generator.rb +24 -0
  21. data/lib/generators/audited/migration.rb +15 -0
  22. data/lib/generators/audited/migration_helper.rb +9 -0
  23. data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
  24. data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
  25. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
  26. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
  27. data/lib/generators/audited/templates/install.rb +30 -0
  28. data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
  29. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
  30. data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
  31. data/lib/generators/audited/upgrade_generator.rb +59 -0
  32. data/spec/audited/audit_spec.rb +246 -0
  33. data/spec/audited/auditor_spec.rb +648 -0
  34. data/spec/audited/sweeper_spec.rb +108 -0
  35. data/spec/audited_spec_helpers.rb +6 -22
  36. data/spec/rails_app/config/environments/test.rb +7 -4
  37. data/spec/rails_app/config/initializers/secret_token.rb +1 -1
  38. data/spec/rails_app/config/routes.rb +1 -4
  39. data/spec/spec_helper.rb +7 -9
  40. data/spec/support/active_record/models.rb +24 -13
  41. data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +12 -0
  42. data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +12 -0
  43. data/spec/support/active_record/schema.rb +37 -12
  44. data/test/db/version_1.rb +4 -4
  45. data/test/db/version_2.rb +4 -4
  46. data/test/db/version_3.rb +4 -4
  47. data/test/db/version_4.rb +4 -4
  48. data/test/db/version_5.rb +2 -2
  49. data/test/db/version_6.rb +2 -2
  50. data/test/install_generator_test.rb +31 -3
  51. data/test/upgrade_generator_test.rb +25 -10
  52. metadata +69 -43
  53. data/CHANGELOG +0 -34
  54. data/lib/audited/active_record/version.rb +0 -5
  55. data/lib/audited/mongo_mapper/version.rb +0 -5
  56. data/spec/support/mongo_mapper/connection.rb +0 -4
  57. data/spec/support/mongo_mapper/models.rb +0 -214
data/lib/audited/audit.rb CHANGED
@@ -1,60 +1,63 @@
1
- module Audited
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
- before_create :set_version_number, :set_audit_user, :set_request_uuid
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
- cattr_accessor :audited_class_names
17
- self.audited_class_names = Set.new
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
- # Returns the list of classes that are being audited
21
- def audited_classes
22
- audited_class_names.map(&:constantize)
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
- # All audits made during the block called will be recorded as made
26
- # by +user+. This method is hopefully threadsafe, making it ideal
27
- # for background operations that require audit information.
28
- def as_user(user, &block)
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
- # @private
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
- # @private
46
- def assign_revision_attributes(record, attributes)
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
- if record.respond_to?("#{attr}=")
51
- record.attributes.has_key?(attr.to_s) ?
52
- record[attr] = val :
53
- record.send("#{attr}=", val)
54
- end
55
- end
56
- record
57
- end
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({ :version => version }))
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.where(
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 = Thread.current[:audited_user] if Thread.current[:audited_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
@@ -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: Audited.audit_class.name
55
+ Audited.audit_class.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: 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
- 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
@@ -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
- 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,32 @@ 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
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( user, &block )
269
- Audited.audit_class.as_user( user, &block )
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("#{name.tableize}_auditing_enabled", true)
305
+ Audited.store.fetch("#{table_name}_auditing_enabled", true)
274
306
  end
275
307
 
276
- def auditing_enabled= val
277
- Audited.store["#{name.tableize}_auditing_enabled"] = val
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
- (!reflection.nil?) &&
171
+ !reflection.nil? &&
168
172
  reflection.macro == :has_many &&
169
173
  reflection.options[:class_name] == Audited.audit_class.name
170
174
  end
@@ -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 < ActionController::Caching::Sweeper
6
- observe Audited.audit_class
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
- attr_accessor :controller
9
- def before(controller)
10
- self.controller = controller
11
- true
12
- end
9
+ delegate :store, to: ::Audited
13
10
 
14
- def after(controller)
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
- end
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 request_uuid
29
- controller.try(:request).try(:uuid)
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 define_callback(klass)
40
- observer = self
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
- ::Audited.store[:current_controller]
33
+ store[:current_controller]
50
34
  end
51
35
 
52
36
  def controller=(value)
53
- ::Audited.store[:current_controller] = value
37
+ store[:current_controller] = value
54
38
  end
55
39
  end
56
40
  end
57
41
 
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
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
- ActionController::Base.class_eval do
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
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.2.1"
2
+ VERSION = "4.4.0"
3
3
  end
@@ -0,0 +1,4 @@
1
+ require 'audited/rspec_matchers'
2
+ module RSpec::Matchers
3
+ include Audited::RspecMatchers
4
+ end
data/lib/audited.rb CHANGED
@@ -1,16 +1,30 @@
1
- require 'rails/observers/active_model/active_model'
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