acts_as_audited_customized 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,26 @@
1
+ acts_as_audited ChangeLog
2
+ -------------------------------------------------------------------------------
3
+ * 2010-12-21 - Allowed customization of the model used to represent a human. [Pat George]
4
+ * 2009-01-27 - Store old and new values for updates, and store all attributes on destroy.
5
+ Refactored revisioning methods to work as expected
6
+ * 2008-10-10 - changed to make it work in development mode
7
+ * 2008-04-19 - refactored to make compatible with dirty tracking in edge rails
8
+ and to stop storing both old and new values in a single audit
9
+ * 2008-04-18 - Fix NoMethodError when trying to access the :previous revision
10
+ on a model that doesn't have previous revisions [Alex Soto]
11
+ * 2008-03-21 - added #changed_attributes to get access to the changes before a
12
+ save [Chris Parker]
13
+ * 2007-12-16 - Added #revision_at for retrieving a revision from a specific
14
+ time [Jacob Atzen]
15
+ * 2007-12-16 - Fix error when getting revision from audit with no changes
16
+ [Geoffrey Wiseman]
17
+ * 2007-12-16 - Remove dependency on acts_as_list
18
+ * 2007-06-17 - Added support getting previous revisions
19
+ * 2006-11-17 - Replaced use of singleton User.current_user with cache sweeper
20
+ implementation for auditing the user that made the change
21
+ * 2006-11-17 - added migration generator
22
+ * 2006-08-14 - incorporated changes from Michael Schuerig to write_attribute
23
+ that saves the new value after every change and not just the
24
+ first, and performs proper type-casting before doing comparisons
25
+ * 2006-08-14 - The "changes" are now saved as a serialized hash
26
+ * 2006-07-21 - initial version
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2008 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.
data/README ADDED
@@ -0,0 +1,70 @@
1
+ = acts_as_audited
2
+
3
+ acts_as_audited is an ActiveRecord extension that logs all changes to your models in an audits table.
4
+
5
+ The purpose of this fork is to store both the previous values and the changed value, making each audit record selfcontained.
6
+
7
+ == Installation
8
+
9
+ * acts_as_audited can be installed as a gem:
10
+
11
+ # config/environment.rb
12
+ config.gem 'acts_as_audited', :lib => false, :source => 'http://gemcutter.org'
13
+
14
+ or a plugin:
15
+
16
+ script/plugin install git://github.com/collectiveidea/acts_as_audited.git
17
+
18
+ * Generate the migration
19
+ script/generate audited_migration add_audits_table
20
+ rake db:migrate
21
+
22
+ == Usage
23
+
24
+ Declare <tt>acts_as_audited</tt> on your models:
25
+
26
+ class User < ActiveRecord::Base
27
+ acts_as_audited :except => [:password, :mistress]
28
+ end
29
+
30
+ Within a web request, will automatically record the user that made the change if your controller has a <tt>current_user</tt> method.
31
+
32
+ To record a user in the audits outside of a web request, you can use <tt>as_user</tt>:
33
+
34
+ Audit.as_user(user) do
35
+ # Perform changes on audited models
36
+ end
37
+
38
+ == Caveats
39
+
40
+ If your model declares +attr_accessible+ after +acts_as_audited+, you need to set +:protect+ to false. acts_as_audited uses +attr_protected+ internally to prevent malicious users from unassociating your audits, and Rails does not allow both +attr_protected+ and +attr_accessible+. It will default to false if +attr_accessible+ is called before +acts_as_audited+, but needs to be explicitly set if it is called after.
41
+
42
+ class User < ActiveRecord::Base
43
+ acts_as_audited :protect => false
44
+ attr_accessible :name
45
+ end
46
+
47
+ == Compatability
48
+
49
+ acts_as_audited works with Rails 2.1 or later.
50
+
51
+ == Getting Help
52
+
53
+ Join the mailing list for getting help or offering suggestions:
54
+ http://groups.google.com/group/acts_as_audited
55
+
56
+ == Contributing
57
+
58
+ Contributions are always welcome. Checkout the latest code on GitHub:
59
+ http://github.com/collectiveidea/acts_as_audited
60
+
61
+ Please include tests with your patches. There are a few gems required to run the tests:
62
+ $ gem install multi_rails
63
+ $ gem install thoughtbot-shoulda jnunemaker-matchy --source http://gems.github.com
64
+
65
+ Make sure the tests pass against all versions of Rails since 2.1:
66
+
67
+ $ rake test:multi_rails:all
68
+
69
+ Please report bugs or feature suggestions on GitHub:
70
+ http://github.com/collectiveidea/acts_as_audited/issues
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rake'
2
+ require 'load_multi_rails_rake_tasks'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ desc 'Default: run tests.'
7
+ task :default => :test
8
+
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gem|
12
+ gem.name = "acts_as_audited_customized"
13
+ gem.summary = %Q{ActiveRecord extension that logs all changes to your models in an audits table additionally allowing you to specify which human model to use (if not 'User')}
14
+ gem.email = "pat.george@gmail.com"
15
+ gem.homepage = "http://github.com/pcg79/acts_as_audited"
16
+ gem.authors = ["Brandon Keepers", "Pat George"]
17
+ gem.add_dependency 'activerecord', '>=2.1'
18
+ gem.add_development_dependency "thoughtbot-shoulda"
19
+ gem.add_development_dependency "jnunemaker-matchy"
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ # Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ desc 'Test the acts_as_audited plugin'
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'lib'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = true
32
+ end
33
+
34
+ task :test => :check_dependencies
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |test|
39
+ test.libs << 'test'
40
+ test.pattern = 'test/**/*_test.rb'
41
+ test.verbose = true
42
+ test.rcov_opts = %w(--exclude test,/usr/lib/ruby,/Library/Ruby,$HOME/.gem --sort coverage)
43
+ end
44
+ rescue LoadError
45
+ task :rcov do
46
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
47
+ end
48
+ end
49
+
50
+ desc 'Generate documentation for the acts_as_audited plugin.'
51
+ Rake::RDocTask.new(:rdoc) do |rdoc|
52
+ rdoc.rdoc_dir = 'doc'
53
+ rdoc.title = 'acts_as_audited'
54
+ rdoc.options << '--line-numbers' << '--inline-source'
55
+ rdoc.rdoc_files.include('README')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.1
@@ -0,0 +1,9 @@
1
+ Description:
2
+ The audit model generator creates the Audit model in app/models/ associating it with the correct "human" class ("user" by default).
3
+
4
+ Example:
5
+ ./script/generate audit_model Person
6
+
7
+ This will create the Audit model in app/models/ associating it to the Person model.
8
+
9
+ You must generate the migration using the same "human" class.
@@ -0,0 +1,21 @@
1
+ class AuditModelGenerator < Rails::Generator::NamedBase
2
+ def initialize(runtime_args, runtime_options = {})
3
+ runtime_args << 'user' if runtime_args.empty?
4
+ super
5
+ @human_model = runtime_args[0] ? runtime_args[0].underscore : 'user'
6
+ end
7
+
8
+ def manifest
9
+ # puts "*** [AuditModelGenerator.manifest] - File.join(File.dirname(__FILE__), '..', '..', 'lib', 'acts_as_audited') = #{File.join(File.dirname(__FILE__), '..', '..', 'lib', 'acts_as_audited')}"
10
+ record do |m|
11
+ m.directory(File.join('app', 'models'))
12
+ m.template('model.rb', "app/models/audit.rb", :assigns => { :human_model => @human_model })
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def banner
19
+ "Usage: #{$0} audit_model [human_model_name]"
20
+ end
21
+ end
@@ -0,0 +1,119 @@
1
+ require 'set'
2
+
3
+ # Audit saves the changes to ActiveRecord models. It has the following attributes:
4
+ #
5
+ # * <tt>auditable</tt>: the ActiveRecord model that was changed
6
+ # * <tt><%= human_model %></tt>: the <%= human_model %> that performed the change; a string or an ActiveRecord model
7
+ # * <tt>action</tt>: one of create, update, or delete
8
+ # * <tt>changes</tt>: a serialized hash of all the changes
9
+ # * <tt>created_at</tt>: Time that the change was performed
10
+ #
11
+ class Audit < ActiveRecord::Base
12
+ belongs_to :auditable, :polymorphic => true
13
+ belongs_to :<%= human_model %>, :polymorphic => true
14
+
15
+ before_create :set_version_number, :set_audit_<%= human_model %>
16
+
17
+ serialize :changes
18
+
19
+ cattr_accessor :audited_class_names
20
+ self.audited_class_names = Set.new
21
+
22
+ def self.audited_classes
23
+ self.audited_class_names.map(&:constantize)
24
+ end
25
+
26
+ # All audits made during the block called will be recorded as made
27
+ # by +<%= human_model %>+. This method is hopefully threadsafe, making it ideal
28
+ # for background operations that require audit information.
29
+ def self.as_<%= human_model %>(<%= human_model %>, &block)
30
+ Thread.current[:acts_as_audited_<%= human_model %>] = <%= human_model %>
31
+
32
+ yield
33
+
34
+ Thread.current[:acts_as_audited_<%= human_model %>] = nil
35
+ end
36
+
37
+ # Allows <%= human_model %> to be set to either a string or an ActiveRecord object
38
+ def <%= human_model %>_as_string=(<%= human_model %>) #:nodoc:
39
+ # reset both either way
40
+ self.<%= human_model %>_as_model = self.username = nil
41
+ <%= human_model %>.is_a?(ActiveRecord::Base) ?
42
+ self.<%= human_model %>_as_model = <%= human_model %> :
43
+ self.username = <%= human_model %>
44
+ end
45
+ alias_method :<%= human_model %>_as_model=, :<%= human_model %>=
46
+ alias_method :<%= human_model %>=, :<%= human_model %>_as_string=
47
+
48
+ def <%= human_model %>_as_string #:nodoc:
49
+ self.<%= human_model %>_as_model || self.username
50
+ end
51
+ alias_method :<%= human_model %>_as_model, :<%= human_model %>
52
+ alias_method :<%= human_model %>, :<%= human_model %>_as_string
53
+
54
+ def revision
55
+ clazz = auditable_type.constantize
56
+ returning clazz.find_by_id(auditable_id) || clazz.new do |m|
57
+ Audit.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge({:version => version}))
58
+ end
59
+ end
60
+
61
+ def ancestors
62
+ self.class.find(:all, :order => 'version',
63
+ :conditions => ['auditable_id = ? and auditable_type = ? and version <= ?',
64
+ auditable_id, auditable_type, version])
65
+ end
66
+
67
+ # Returns a hash of the changed attributes with the new values
68
+ def new_attributes
69
+ (changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
70
+ attrs[attr] = Array(values).last
71
+ attrs
72
+ end
73
+ end
74
+
75
+ # Returns a hash of the changed attributes with the old values
76
+ def old_attributes
77
+ (changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
78
+ attrs[attr] = Array(values).first
79
+ attrs
80
+ end
81
+ end
82
+
83
+ def self.reconstruct_attributes(audits)
84
+ attributes = {}
85
+ result = audits.collect do |audit|
86
+ attributes.merge!(audit.new_attributes).merge!(:version => audit.version)
87
+ yield attributes if block_given?
88
+ end
89
+ block_given? ? result : attributes
90
+ end
91
+
92
+ def self.assign_revision_attributes(record, attributes)
93
+ attributes.each do |attr, val|
94
+ if record.respond_to?("#{attr}=")
95
+ record.attributes.has_key?(attr.to_s) ?
96
+ record[attr] = val :
97
+ record.send("#{attr}=", val)
98
+ end
99
+ end
100
+ record
101
+ end
102
+
103
+ private
104
+
105
+ def set_version_number
106
+ max = self.class.maximum(:version,
107
+ :conditions => {
108
+ :auditable_id => auditable_id,
109
+ :auditable_type => auditable_type
110
+ }) || 0
111
+ self.version = max + 1
112
+ end
113
+
114
+ def set_audit_<%= human_model %>
115
+ self.<%= human_model %> = Thread.current[:acts_as_audited_<%= human_model %>] if Thread.current[:acts_as_audited_<%= human_model %>]
116
+ nil # prevent stopping callback chains
117
+ end
118
+
119
+ end
@@ -0,0 +1,7 @@
1
+ Description:
2
+ The audited migration generator creates a migration to add the audits table.
3
+
4
+ Example:
5
+ ./script/generate audited_migration add_audits_table
6
+
7
+ This will create a migration in db/migrate/. Run "rake db:migrate" to update your database.
@@ -0,0 +1,19 @@
1
+ class AuditedMigrationGenerator < Rails::Generator::NamedBase
2
+ def initialize(runtime_args, runtime_options = {})
3
+ super
4
+ @human_model = runtime_args[1] ? runtime_args[1].underscore : 'user'
5
+ end
6
+
7
+ def manifest
8
+ record do |m|
9
+ m.directory(File.join('db', 'migrate'))
10
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => { :human_model => @human_model }
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def banner
17
+ "Usage: #{$0} audited_migration add_audits_table [human_model_name]"
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ class <%= 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 :<%= human_model %>_id, :integer
7
+ t.column :<%= human_model %>_type, :string
8
+ t.column :username, :string
9
+ t.column :action, :string
10
+ t.column :changes, :text
11
+ t.column :version, :integer, :default => 0
12
+ t.column :created_at, :datetime
13
+ end
14
+
15
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
16
+ add_index :audits, [:<%= human_model %>_id, :<%= human_model %>_type], :name => '<%= human_model %>_index'
17
+ add_index :audits, :created_at
18
+ end
19
+
20
+ def self.down
21
+ drop_table :audits
22
+ end
23
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
@@ -0,0 +1,258 @@
1
+ # Copyright (c) 2006 Brandon Keepers
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ module CollectiveIdea #:nodoc:
23
+ module Acts #:nodoc:
24
+ # Specify this act if you want changes to your model to be saved in an
25
+ # audit table. This assumes there is an audits table ready.
26
+ #
27
+ # class User < ActiveRecord::Base
28
+ # acts_as_audited
29
+ # end
30
+ #
31
+ # See <tt>CollectiveIdea::Acts::Audited::ClassMethods#acts_as_audited</tt>
32
+ # for configuration options
33
+ module Audited #:nodoc:
34
+ CALLBACKS = [:audit_create, :audit_update, :audit_destroy]
35
+
36
+ # The name of the model used to represent a human. Default: :user
37
+ mattr_accessor :human_model
38
+ @@human_model = :user
39
+
40
+ class << self
41
+ # Call this method to modify defaults in your initializers.
42
+ #
43
+ # @example
44
+ # Audited.configure do |config|
45
+ # config.human_model = :person
46
+ # end
47
+ def configure
48
+ yield self
49
+ end
50
+
51
+ def included(base) # :nodoc:
52
+ base.extend ClassMethods
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ # == Configuration options
58
+ #
59
+ #
60
+ # * +only+ - Only audit the given attributes
61
+ # * +except+ - Excludes fields from being saved in the audit log.
62
+ # By default, acts_as_audited will audit all but these fields:
63
+ #
64
+ # [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
65
+ # You can add to those by passing one or an array of fields to skip.
66
+ #
67
+ # class User < ActiveRecord::Base
68
+ # acts_as_audited :except => :password
69
+ # end
70
+ # * +protect+ - If your model uses +attr_protected+, set this to false to prevent Rails from
71
+ # raising an error. If you declare +attr_accessibe+ before calling +acts_as_audited+, it
72
+ # will automatically default to false. You only need to explicitly set this if you are
73
+ # calling +attr_accessible+ after.
74
+ #
75
+ # class User < ActiveRecord::Base
76
+ # acts_as_audited :protect => false
77
+ # attr_accessible :name
78
+ # end
79
+ #
80
+ def acts_as_audited(options = {})
81
+ # don't allow multiple calls
82
+ return if self.included_modules.include?(CollectiveIdea::Acts::Audited::InstanceMethods)
83
+
84
+ options = {:protect => accessible_attributes.nil?}.merge(options)
85
+
86
+ class_inheritable_reader :non_audited_columns
87
+ class_inheritable_reader :auditing_enabled
88
+
89
+ if options[:only]
90
+ except = self.column_names - options[:only].flatten.map(&:to_s)
91
+ else
92
+ except = [self.primary_key, inheritance_column, 'lock_version',
93
+ 'created_at', 'updated_at', 'created_on', 'updated_on']
94
+ except |= Array(options[:except]).collect(&:to_s) if options[:except]
95
+ end
96
+ write_inheritable_attribute :non_audited_columns, except
97
+
98
+ has_many :audits, :as => :auditable, :order => "#{Audit.quoted_table_name}.version"
99
+ attr_protected :audit_ids if options[:protect]
100
+ Audit.audited_class_names << self.to_s
101
+
102
+ after_create :audit_create if !options[:on] || (options[:on] && options[:on].include?(:create))
103
+ before_update :audit_update if !options[:on] || (options[:on] && options[:on].include?(:update))
104
+ after_destroy :audit_destroy if !options[:on] || (options[:on] && options[:on].include?(:destroy))
105
+
106
+ attr_accessor :version
107
+
108
+ extend CollectiveIdea::Acts::Audited::SingletonMethods
109
+ include CollectiveIdea::Acts::Audited::InstanceMethods
110
+
111
+ write_inheritable_attribute :auditing_enabled, true
112
+ end
113
+ end
114
+
115
+ module InstanceMethods
116
+
117
+ # Temporarily turns off auditing while saving.
118
+ def save_without_auditing
119
+ without_auditing { save }
120
+ end
121
+
122
+ # Executes the block with the auditing callbacks disabled.
123
+ #
124
+ # @foo.without_auditing do
125
+ # @foo.save
126
+ # end
127
+ #
128
+ def without_auditing(&block)
129
+ self.class.without_auditing(&block)
130
+ end
131
+
132
+ # Gets an array of the revisions available
133
+ #
134
+ # user.revisions.each do |revision|
135
+ # user.name
136
+ # user.version
137
+ # end
138
+ #
139
+ def revisions(from_version = 1)
140
+ audits = self.audits.find(:all, :conditions => ['version >= ?', from_version])
141
+ return [] if audits.empty?
142
+ revision = self.audits.find_by_version(from_version).revision
143
+ Audit.reconstruct_attributes(audits) {|attrs| revision.revision_with(attrs) }
144
+ end
145
+
146
+ # Get a specific revision specified by the version number, or +:previous+
147
+ def revision(version)
148
+ revision_with Audit.reconstruct_attributes(audits_to(version))
149
+ end
150
+
151
+ def revision_at(date_or_time)
152
+ audits = self.audits.find(:all, :conditions => ["created_at <= ?", date_or_time])
153
+ revision_with Audit.reconstruct_attributes(audits) unless audits.empty?
154
+ end
155
+
156
+ def audited_attributes
157
+ attributes.except(*non_audited_columns)
158
+ end
159
+
160
+ protected
161
+
162
+ def revision_with(attributes)
163
+ returning self.dup do |revision|
164
+ revision.send :instance_variable_set, '@attributes', self.attributes_before_type_cast
165
+ Audit.assign_revision_attributes(revision, attributes)
166
+
167
+ # Remove any association proxies so that they will be recreated
168
+ # and reference the correct object for this revision. The only way
169
+ # to determine if an instance variable is a proxy object is to
170
+ # see if it responds to certain methods, as it forwards almost
171
+ # everything to its target.
172
+ for ivar in revision.instance_variables
173
+ proxy = revision.instance_variable_get ivar
174
+ if !proxy.nil? and proxy.respond_to? :proxy_respond_to?
175
+ revision.instance_variable_set ivar, nil
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def audited_changes
184
+ changed_attributes.except(*non_audited_columns).inject({}) do |changes,(attr, old_value)|
185
+ changes[attr] = [old_value, self[attr]]
186
+ changes
187
+ end
188
+ end
189
+
190
+ def audits_to(version = nil)
191
+ if version == :previous
192
+ version = if self.version
193
+ self.version - 1
194
+ else
195
+ previous = audits.find(:first, :offset => 1,
196
+ :order => "#{Audit.quoted_table_name}.version DESC")
197
+ previous ? previous.version : 1
198
+ end
199
+ end
200
+ audits.find(:all, :conditions => ['version <= ?', version])
201
+ end
202
+
203
+ def audit_create
204
+ write_audit(:action => 'create', :changes => audited_attributes)
205
+ end
206
+
207
+ def audit_update
208
+ unless (changes = audited_changes).empty?
209
+ write_audit(:action => 'update', :changes => changes)
210
+ end
211
+ end
212
+
213
+ def audit_destroy
214
+ write_audit(:action => 'destroy', :changes => audited_attributes)
215
+ end
216
+
217
+ def write_audit(attrs)
218
+ self.audits.create attrs if auditing_enabled
219
+ end
220
+ end # InstanceMethods
221
+
222
+ module SingletonMethods
223
+ # Returns an array of columns that are audited. See non_audited_columns
224
+ def audited_columns
225
+ self.columns.select { |c| !non_audited_columns.include?(c.name) }
226
+ end
227
+
228
+ # Executes the block with auditing disabled.
229
+ #
230
+ # Foo.without_auditing do
231
+ # @foo.save
232
+ # end
233
+ #
234
+ def without_auditing(&block)
235
+ auditing_was_enabled = auditing_enabled
236
+ disable_auditing
237
+ returning(block.call) { enable_auditing if auditing_was_enabled }
238
+ end
239
+
240
+ def disable_auditing
241
+ write_inheritable_attribute :auditing_enabled, false
242
+ end
243
+
244
+ def enable_auditing
245
+ write_inheritable_attribute :auditing_enabled, true
246
+ end
247
+
248
+ # All audit operations during the block are recorded as being
249
+ # made by +user+. This is not model specific, the method is a
250
+ # convenience wrapper around #Audit.as_user.
251
+ def audit_as( user, &block )
252
+ Audit.as_user( user, &block )
253
+ end
254
+
255
+ end
256
+ end
257
+ end
258
+ end