notifiably_audited 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +8 -8
  2. data/Appraisals +11 -0
  3. data/CHANGELOG +34 -0
  4. data/LICENSE +19 -0
  5. data/audited-activerecord.gemspec +21 -0
  6. data/audited-mongo_mapper.gemspec +21 -0
  7. data/audited.gemspec +26 -0
  8. data/gemfiles/rails30.gemfile +7 -0
  9. data/gemfiles/rails31.gemfile +7 -0
  10. data/gemfiles/rails32.gemfile +7 -0
  11. data/lib/audited.rb +15 -0
  12. data/lib/audited/audit.rb +102 -0
  13. data/lib/audited/auditor.rb +270 -0
  14. data/lib/audited/rspec_matchers.rb +173 -0
  15. data/lib/audited/sweeper.rb +51 -0
  16. data/notifiably_audited.gemspec +11 -18
  17. data/spec/audited_spec_helpers.rb +31 -0
  18. data/spec/rails_app/config/application.rb +5 -0
  19. data/spec/rails_app/config/database.yml +24 -0
  20. data/spec/rails_app/config/environment.rb +5 -0
  21. data/spec/rails_app/config/environments/development.rb +19 -0
  22. data/spec/rails_app/config/environments/production.rb +33 -0
  23. data/spec/rails_app/config/environments/test.rb +33 -0
  24. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  25. data/spec/rails_app/config/initializers/inflections.rb +2 -0
  26. data/spec/rails_app/config/initializers/secret_token.rb +2 -0
  27. data/spec/rails_app/config/routes.rb +6 -0
  28. data/spec/spec_helper.rb +23 -0
  29. data/spec/support/active_record/models.rb +84 -0
  30. data/spec/support/active_record/schema.rb +54 -0
  31. data/spec/support/mongo_mapper/connection.rb +4 -0
  32. data/spec/support/mongo_mapper/models.rb +210 -0
  33. data/test/db/version_1.rb +17 -0
  34. data/test/db/version_2.rb +18 -0
  35. data/test/db/version_3.rb +19 -0
  36. data/test/db/version_4.rb +20 -0
  37. data/test/db/version_5.rb +18 -0
  38. data/test/install_generator_test.rb +17 -0
  39. data/test/test_helper.rb +19 -0
  40. data/test/upgrade_generator_test.rb +65 -0
  41. metadata +56 -2
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ODcwNjQ2YTAzNzU1ZmRmNTM5MWRlYjIyMWJkMjA5YzZhMzljMTU2Ng==
4
+ MjE4ZTc3NjA5YWUxYzdlM2YwODc3N2UwMTM0NDk1NGQwNzFjNzk0OQ==
5
5
  data.tar.gz: !binary |-
6
- NGJiZWFlNTFkZDEzOWIxOTMyYjZlYzgyZjE4MTI2NTU3N2JhMzYzNg==
6
+ NGVlN2YzNjMxMWI1MGMxZWQwYzgzMmQzOTI5MGJlNzk0NmIxZGY5MA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NjUxZDhlZDNlZTY2OWVmNzI3MTY0ZTczN2E3ZGY3MjdkMzk5NTc3MWNkOTBj
10
- NDUxNjA5NzQwNjNhOTBkZGMxNTZhYzAzMjBhMTNlNDZmZTMyZmM4YzVlN2Ew
11
- MTM2YTRkYzIyZWI4MDljMmIxMDRhZTY3ZWY4ZDZiNjhiZjU4YmE=
9
+ YTVlM2QwMzliMDViNDQxNmM2NDIxOTQ1NDYxMzFhMzQyYWNjYTQ2OWU3NGE0
10
+ YzhkZmYwYmY3MTZmYWM0MTFmNDVkN2M3N2ZlNzIyNDE4MmUxZmYyYTY3MmUx
11
+ OTIwMjUxZjY4MGUwYjgzNzc2MDQzNzAxNTUxOTcxMDY3M2UyZDY=
12
12
  data.tar.gz: !binary |-
13
- OTdlMGIxMmM0YTNiMDkxN2QwZWFjMjVkODc1M2Q1YmQ4ZWVmMWFkZjkyMmY5
14
- ZmY5OWVlOTU5NGI5ZTE2MjU1MmM5MjJlNjg2MjJjNmQxMzFjOWUzMTUyYTFh
15
- ZWI0ZTE5N2I5MWE3YjhmYzhlN2I0ZWY4YWJjODI4MmY3Mzc0NTY=
13
+ MmYxOTczOWU2ZGRkM2IxYzFlNWIzMjhhZTRiZTIyNzJlOWY0MDA4NDU0NmVl
14
+ NzQ4NWU2NWQxYjcxNWEyYThlNWRlYjc5ZTFhMGU3ZjVmY2UyNDg1MmVmY2Qx
15
+ OTE2ZDI2YjkxNzBjMjcxOGQ4MzNlZjNjZWY1ZWNmNDE2YWNjOGM=
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise 'rails30' do
2
+ gem 'rails', '~> 3.0.0'
3
+ end
4
+
5
+ appraise 'rails31' do
6
+ gem 'rails', '~> 3.1.0'
7
+ end
8
+
9
+ appraise 'rails32' do
10
+ gem 'rails', '~> 3.2.0'
11
+ end
data/CHANGELOG ADDED
@@ -0,0 +1,34 @@
1
+ Audited ChangeLog
2
+ -------------------------------------------------------------------------------
3
+ * 2012-04-10 - Add Audit scopes for creates, updates and destroys [chriswfx]
4
+ * 2011-10-25 - Made ignored_attributes configurable [senny]
5
+ * 2011-09-09 - Rails 3.x support
6
+ Support for associated audits
7
+ Support for remote IP address storage
8
+ Plenty of bug fixes and refactoring
9
+ [kennethkalmer, ineu, PatrickMa, jrozner, dwarburton, bsiggelkow, dgm]
10
+ * 2009-01-27 - Store old and new values for updates, and store all attributes on destroy.
11
+ Refactored revisioning methods to work as expected
12
+ * 2008-10-10 - changed to make it work in development mode
13
+ * 2008-09-24 - Add ability to record parent record of the record being audited
14
+ [Kenneth Kalmer]
15
+ * 2008-04-19 - refactored to make compatible with dirty tracking in edge rails
16
+ and to stop storing both old and new values in a single audit
17
+ * 2008-04-18 - Fix NoMethodError when trying to access the :previous revision
18
+ on a model that doesn't have previous revisions [Alex Soto]
19
+ * 2008-03-21 - added #changed_attributes to get access to the changes before a
20
+ save [Chris Parker]
21
+ * 2007-12-16 - Added #revision_at for retrieving a revision from a specific
22
+ time [Jacob Atzen]
23
+ * 2007-12-16 - Fix error when getting revision from audit with no changes
24
+ [Geoffrey Wiseman]
25
+ * 2007-12-16 - Remove dependency on acts_as_list
26
+ * 2007-06-17 - Added support getting previous revisions
27
+ * 2006-11-17 - Replaced use of singleton User.current_user with cache sweeper
28
+ implementation for auditing the user that made the change
29
+ * 2006-11-17 - added migration generator
30
+ * 2006-08-14 - incorporated changes from Michael Schuerig to write_attribute
31
+ that saves the new value after every change and not just the
32
+ first, and performs proper type-casting before doing comparisons
33
+ * 2006-08-14 - The "changes" are now saved as a serialized hash
34
+ * 2006-07-21 - initial version
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2010 Brandon Keepers - Collective Idea
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'audited-activerecord'
5
+ gem.version = '3.0.0'
6
+
7
+ gem.authors = ['Brandon Keepers', 'Kenneth Kalmer', 'Daniel Morrison', 'Brian Ryckbost', 'Steve Richert', 'Ryan Glover']
8
+ gem.email = 'info@collectiveidea.com'
9
+ gem.description = 'Log all changes to your ActiveRecord models'
10
+ gem.summary = gem.description
11
+ gem.homepage = 'https://github.com/collectiveidea/audited'
12
+ gem.license = 'MIT'
13
+
14
+ gem.add_dependency 'audited', gem.version
15
+ gem.add_dependency 'activerecord', '~> 3.0'
16
+
17
+ gem.files = `git ls-files lib`.split($\).grep(/(active_?record|generators)/)
18
+ gem.files << 'LICENSE'
19
+ gem.require_paths = ['lib']
20
+ end
21
+
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'audited-mongo_mapper'
5
+ gem.version = '3.0.0'
6
+
7
+ gem.authors = ['Brandon Keepers', 'Kenneth Kalmer', 'Daniel Morrison', 'Brian Ryckbost', 'Steve Richert', 'Ryan Glover']
8
+ gem.email = 'info@collectiveidea.com'
9
+ gem.description = 'Log all changes to your MongoMapper models'
10
+ gem.summary = gem.description
11
+ gem.homepage = 'https://github.com/collectiveidea/audited'
12
+ gem.license = 'MIT'
13
+
14
+ gem.add_dependency 'audited', gem.version
15
+ gem.add_dependency 'mongo_mapper', '~> 0.11'
16
+
17
+ gem.files = `git ls-files lib`.split($\).grep(/mongo_mapper/)
18
+ gem.files << 'LICENSE'
19
+ gem.require_paths = ['lib']
20
+ end
21
+
data/audited.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'notifiably_audited'
5
+ gem.version = '0.0.2'
6
+
7
+ gem.authors = ['Brandon Keepers', 'Kenneth Kalmer', 'Daniel Morrison', 'Brian Ryckbost', 'Steve Richert', 'Ryan Glover']
8
+ gem.email = 'info@collectiveidea.com'
9
+ gem.description = 'Log all changes to your models'
10
+ gem.summary = gem.description
11
+ gem.homepage = 'https://github.com/collectiveidea/audited'
12
+ gem.license = 'MIT'
13
+
14
+ gem.add_development_dependency 'activerecord', '~> 3.0'
15
+ gem.add_development_dependency 'appraisal', '~> 0.4'
16
+ gem.add_development_dependency 'bson_ext', '~> 1.6'
17
+ gem.add_development_dependency 'mongo_mapper', '~> 0.11'
18
+ gem.add_development_dependency 'rails', '~> 3.0'
19
+ gem.add_development_dependency 'rspec-rails', '~> 2.0'
20
+ gem.add_development_dependency 'sqlite3', '~> 1.0'
21
+
22
+ gem.files = `git ls-files`.split($\).reject{|f| f =~ /(lib\/audited\-|adapters|generators)/ }
23
+ gem.test_files = gem.files.grep(/^spec\//)
24
+ gem.require_paths = ['lib']
25
+ end
26
+
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 3.0.0"
6
+
7
+ gemspec :name=>"audited", :path=>"../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 3.1.0"
6
+
7
+ gemspec :name=>"audited", :path=>"../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 3.2.0"
6
+
7
+ gemspec :name=>"audited", :path=>"../"
data/lib/audited.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Audited
2
+ VERSION = '3.0.0'
3
+
4
+ class << self
5
+ attr_accessor :ignored_attributes, :current_user_method, :audit_class
6
+
7
+ def store
8
+ Thread.current[:audited_store] ||= {}
9
+ end
10
+ end
11
+
12
+ @ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
13
+
14
+ @current_user_method = :current_user
15
+ end
@@ -0,0 +1,102 @@
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
13
+
14
+ before_create :set_version_number, :set_audit_user
15
+
16
+ cattr_accessor :audited_class_names
17
+ self.audited_class_names = Set.new
18
+
19
+ attr_accessible :action, :audited_changes, :comment, :associated
20
+ end
21
+
22
+ # Returns the list of classes that are being audited
23
+ def audited_classes
24
+ audited_class_names.map(&:constantize)
25
+ end
26
+
27
+ # All audits made during the block called will be recorded as made
28
+ # by +user+. This method is hopefully threadsafe, making it ideal
29
+ # for background operations that require audit information.
30
+ def as_user(user, &block)
31
+ Thread.current[:audited_user] = user
32
+ yield
33
+ ensure
34
+ Thread.current[:audited_user] = nil
35
+ end
36
+
37
+ # @private
38
+ def reconstruct_attributes(audits)
39
+ attributes = {}
40
+ result = audits.collect do |audit|
41
+ attributes.merge!(audit.new_attributes).merge!(:version => audit.version)
42
+ yield attributes if block_given?
43
+ end
44
+ block_given? ? result : attributes
45
+ end
46
+
47
+ # @private
48
+ def assign_revision_attributes(record, attributes)
49
+ attributes.each do |attr, val|
50
+ record = record.dup if record.frozen?
51
+
52
+ if record.respond_to?("#{attr}=")
53
+ record.attributes.has_key?(attr.to_s) ?
54
+ record[attr] = val :
55
+ record.send("#{attr}=", val)
56
+ end
57
+ end
58
+ record
59
+ end
60
+ end
61
+
62
+ # Return an instance of what the object looked like at this revision. If
63
+ # the object has been destroyed, this will be a new record.
64
+ def revision
65
+ clazz = auditable_type.constantize
66
+ (clazz.find_by_id(auditable_id) || clazz.new).tap do |m|
67
+ self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge({ :version => version }))
68
+ end
69
+ end
70
+
71
+ # Returns a hash of the changed attributes with the new values
72
+ def new_attributes
73
+ (audited_changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
74
+ attrs[attr] = values.is_a?(Array) ? values.last : values
75
+ attrs
76
+ end
77
+ end
78
+
79
+ # Returns a hash of the changed attributes with the old values
80
+ def old_attributes
81
+ (audited_changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
82
+ attrs[attr] = Array(values).first
83
+
84
+ attrs
85
+ end
86
+ end
87
+
88
+ private
89
+ def set_version_number
90
+ max = self.class.where(
91
+ :auditable_id => auditable_id,
92
+ :auditable_type => auditable_type
93
+ ).order(:version.desc).first.try(:version) || 0
94
+ self.version = max + 1
95
+ end
96
+
97
+ def set_audit_user
98
+ self.user = Thread.current[:audited_user] if Thread.current[:audited_user]
99
+ nil # prevent stopping callback chains
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,270 @@
1
+ module Audited
2
+ # Specify this act if you want changes to your model to be saved in an
3
+ # audit table. This assumes there is an audits table ready.
4
+ #
5
+ # class User < ActiveRecord::Base
6
+ # audited
7
+ # end
8
+ #
9
+ # To store an audit comment set model.audit_comment to your comment before
10
+ # a create, update or destroy operation.
11
+ #
12
+ # See <tt>Audited::Adapters::ActiveRecord::Auditor::ClassMethods#audited</tt>
13
+ # for configuration options
14
+ module Auditor #:nodoc:
15
+ extend ActiveSupport::Concern
16
+
17
+ CALLBACKS = [:audit_create, :audit_update, :audit_destroy]
18
+
19
+ module ClassMethods
20
+ # == Configuration options
21
+ #
22
+ #
23
+ # * +only+ - Only audit the given attributes
24
+ # * +except+ - Excludes fields from being saved in the audit log.
25
+ # By default, Audited will audit all but these fields:
26
+ #
27
+ # [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
28
+ # You can add to those by passing one or an array of fields to skip.
29
+ #
30
+ # class User < ActiveRecord::Base
31
+ # audited :except => :password
32
+ # end
33
+ # * +protect+ - If your model uses +attr_protected+, set this to false to prevent Rails from
34
+ # raising an error. If you declare +attr_accessible+ before calling +audited+, it
35
+ # will automatically default to false. You only need to explicitly set this if you are
36
+ # calling +attr_accessible+ after.
37
+ #
38
+ # * +require_comment+ - Ensures that audit_comment is supplied before
39
+ # any create, update or destroy operation.
40
+ #
41
+ # class User < ActiveRecord::Base
42
+ # audited :protect => false
43
+ # attr_accessible :name
44
+ # end
45
+ #
46
+ def audited(options = {})
47
+ # don't allow multiple calls
48
+ return if self.included_modules.include?(Audited::Auditor::AuditedInstanceMethods)
49
+
50
+ class_attribute :non_audited_columns, :instance_writer => false
51
+ class_attribute :auditing_enabled, :instance_writer => false
52
+ class_attribute :audit_associated_with, :instance_writer => false
53
+
54
+ if options[:only]
55
+ except = self.column_names - options[:only].flatten.map(&:to_s)
56
+ else
57
+ except = default_ignored_attributes + Audited.ignored_attributes
58
+ except |= Array(options[:except]).collect(&:to_s) if options[:except]
59
+ end
60
+ self.non_audited_columns = except
61
+ self.audit_associated_with = options[:associated_with]
62
+
63
+ if options[:comment_required]
64
+ validates_presence_of :audit_comment, :if => :auditing_enabled
65
+ before_destroy :require_comment
66
+ end
67
+
68
+ attr_accessor :audit_comment
69
+ unless options[:allow_mass_assignment]
70
+ attr_accessible :audit_comment
71
+ end
72
+
73
+ has_many :audits, :as => :auditable, :class_name => Audited.audit_class.name
74
+ Audited.audit_class.audited_class_names << self.to_s
75
+
76
+ after_create :audit_create if !options[:on] || (options[:on] && options[:on].include?(:create))
77
+ before_update :audit_update if !options[:on] || (options[:on] && options[:on].include?(:update))
78
+ before_destroy :audit_destroy if !options[:on] || (options[:on] && options[:on].include?(:destroy))
79
+
80
+ # Define and set an after_audit callback. This might be useful if you want
81
+ # to notify a party after the audit has been created.
82
+ define_callbacks :audit
83
+ set_callback :audit, :after, :after_audit, :if => lambda { self.respond_to?(:after_audit) }
84
+
85
+ attr_accessor :version
86
+
87
+ extend Audited::Auditor::AuditedClassMethods
88
+ include Audited::Auditor::AuditedInstanceMethods
89
+
90
+ self.auditing_enabled = true
91
+ end
92
+
93
+ def has_associated_audits
94
+ has_many :associated_audits, :as => :associated, :class_name => Audited.audit_class.name
95
+ end
96
+ end
97
+
98
+ module AuditedInstanceMethods
99
+ # Temporarily turns off auditing while saving.
100
+ def save_without_auditing
101
+ without_auditing { save }
102
+ end
103
+
104
+ # Executes the block with the auditing callbacks disabled.
105
+ #
106
+ # @foo.without_auditing do
107
+ # @foo.save
108
+ # end
109
+ #
110
+ def without_auditing(&block)
111
+ self.class.without_auditing(&block)
112
+ end
113
+
114
+ # Gets an array of the revisions available
115
+ #
116
+ # user.revisions.each do |revision|
117
+ # user.name
118
+ # user.version
119
+ # end
120
+ #
121
+ def revisions(from_version = 1)
122
+ audits = self.audits.from_version(from_version)
123
+ return [] if audits.empty?
124
+ revisions = []
125
+ audits.each do |audit|
126
+ revisions << audit.revision
127
+ end
128
+ revisions
129
+ end
130
+
131
+ # Get a specific revision specified by the version number, or +:previous+
132
+ def revision(version)
133
+ revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
134
+ end
135
+
136
+ # Find the oldest revision recorded prior to the date/time provided.
137
+ def revision_at(date_or_time)
138
+ audits = self.audits.up_until(date_or_time)
139
+ revision_with Audited.audit_class.reconstruct_attributes(audits) unless audits.empty?
140
+ end
141
+
142
+ # List of attributes that are audited.
143
+ def audited_attributes
144
+ attributes.except(*non_audited_columns)
145
+ end
146
+
147
+ protected
148
+
149
+ def revision_with(attributes)
150
+ self.dup.tap do |revision|
151
+ revision.id = id
152
+ revision.send :instance_variable_set, '@attributes', self.attributes
153
+ revision.send :instance_variable_set, '@new_record', self.destroyed?
154
+ revision.send :instance_variable_set, '@persisted', !self.destroyed?
155
+ revision.send :instance_variable_set, '@readonly', false
156
+ revision.send :instance_variable_set, '@destroyed', false
157
+ revision.send :instance_variable_set, '@_destroyed', false
158
+ revision.send :instance_variable_set, '@marked_for_destruction', false
159
+ Audited.audit_class.assign_revision_attributes(revision, attributes)
160
+
161
+ # Remove any association proxies so that they will be recreated
162
+ # and reference the correct object for this revision. The only way
163
+ # to determine if an instance variable is a proxy object is to
164
+ # see if it responds to certain methods, as it forwards almost
165
+ # everything to its target.
166
+ for ivar in revision.instance_variables
167
+ proxy = revision.instance_variable_get ivar
168
+ if !proxy.nil? and proxy.respond_to? :proxy_respond_to?
169
+ revision.instance_variable_set ivar, nil
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ private
176
+
177
+ def audited_changes
178
+ changed_attributes.except(*non_audited_columns).inject({}) do |changes,(attr, old_value)|
179
+ changes[attr] = [old_value, self[attr]]
180
+ changes
181
+ end
182
+ end
183
+
184
+ def audits_to(version = nil)
185
+ if version == :previous
186
+ version = if self.version
187
+ self.version - 1
188
+ else
189
+ previous = audits.descending.offset(1).first
190
+ previous ? previous.version : 1
191
+ end
192
+ end
193
+ audits.to_version(version)
194
+ end
195
+
196
+ def audit_create
197
+ write_audit(:action => 'create', :audited_changes => audited_attributes,
198
+ :comment => audit_comment)
199
+ end
200
+
201
+ def audit_update
202
+ unless (changes = audited_changes).empty? && audit_comment.blank?
203
+ write_audit(:action => 'update', :audited_changes => changes,
204
+ :comment => audit_comment)
205
+ end
206
+ end
207
+
208
+ def audit_destroy
209
+ write_audit(:action => 'destroy', :audited_changes => audited_attributes,
210
+ :comment => audit_comment)
211
+ end
212
+
213
+ def write_audit(attrs)
214
+ attrs[:associated] = self.send(audit_associated_with) unless audit_associated_with.nil?
215
+ self.audit_comment = nil
216
+ run_callbacks(:audit) { self.audits.create(attrs) } if auditing_enabled
217
+ end
218
+
219
+ def require_comment
220
+ if auditing_enabled && audit_comment.blank?
221
+ errors.add(:audit_comment, "Comment required before destruction")
222
+ return false
223
+ end
224
+ end
225
+
226
+ CALLBACKS.each do |attr_name|
227
+ alias_method "#{attr_name}_callback".to_sym, attr_name
228
+ end
229
+
230
+ def empty_callback #:nodoc:
231
+ end
232
+
233
+ end # InstanceMethods
234
+
235
+ module AuditedClassMethods
236
+ # Returns an array of columns that are audited. See non_audited_columns
237
+ def audited_columns
238
+ self.columns.select { |c| !non_audited_columns.include?(c.name) }
239
+ end
240
+
241
+ # Executes the block with auditing disabled.
242
+ #
243
+ # Foo.without_auditing do
244
+ # @foo.save
245
+ # end
246
+ #
247
+ def without_auditing(&block)
248
+ auditing_was_enabled = auditing_enabled
249
+ disable_auditing
250
+ block.call.tap { enable_auditing if auditing_was_enabled }
251
+ end
252
+
253
+ def disable_auditing
254
+ self.auditing_enabled = false
255
+ end
256
+
257
+ def enable_auditing
258
+ self.auditing_enabled = true
259
+ end
260
+
261
+ # All audit operations during the block are recorded as being
262
+ # made by +user+. This is not model specific, the method is a
263
+ # convenience wrapper around
264
+ # @see Audit#as_user.
265
+ def audit_as( user, &block )
266
+ Audited.audit_class.as_user( user, &block )
267
+ end
268
+ end
269
+ end
270
+ end