acts_as_audited_customized 1.2.1

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.
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