revo-acts_as_audited 1.0.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.
@@ -0,0 +1,4 @@
1
+ .idea/
2
+ acts_as_audited_plugin.sqlite3.db
3
+ test/debug.log
4
+ coverage/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 =
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.
@@ -0,0 +1,83 @@
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
+ * Install the plugin into your rails app
10
+ If you are using Rails 2.1:
11
+
12
+ script/plugin install git://github.com/collectiveidea/acts_as_audited.git
13
+
14
+ For versions prior to 2.1:
15
+
16
+ git clone git://github.com/collectiveidea/acts_as_audited.git vendor/plugins/acts_as_audited
17
+
18
+ * Generate the migration
19
+ script/generate audited_migration add_audits_table
20
+ rake db:migrate
21
+
22
+ == Usage
23
+
24
+ If you're using acts_as_audited within Rails, you can simply declare which models should be audited. acts_as_audited can also automatically record the user that made the change if your controller has a <tt>current_user</tt> method.
25
+
26
+ class ApplicationController < ActionController::Base
27
+ audit User, List, Item => {:except => :password}
28
+ protected
29
+ def current_user
30
+ @user ||= User.find(session[:user])
31
+ end
32
+ end
33
+
34
+ To get auditing outside of Rails you can explicitly declare <tt>acts_as_audited</tt> on your models:
35
+
36
+ class User < ActiveRecord::Base
37
+ acts_as_audited :except => [:password, :mistress]
38
+ end
39
+
40
+ To record a user in the audits when the sweepers are not available, you can use <tt>as_user</tt>:
41
+
42
+ Audit.as_user( user ) do
43
+ # Perform changes on audited models
44
+ end
45
+
46
+ See http://opensoul.org/2006/07/21/acts_as_audited for more information.
47
+
48
+ == Caveats
49
+
50
+ 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.
51
+
52
+ class User < ActiveRecord::Base
53
+ acts_as_audited :protect => false
54
+ attr_accessible :name
55
+ end
56
+
57
+ === ActiveScaffold
58
+
59
+ Many users have also reported problems with acts_as_audited and ActiveScaffold, which appears to be caused by a limitation in ActiveScaffold not supporting polymorphic associations. To get it to work with ActiveScaffold:
60
+
61
+ class ApplicationController < ActionController::Base
62
+ audit MyModel, :only => [:create, :update, :destroy]
63
+ end
64
+
65
+ == Compatability
66
+
67
+ acts_as_audited works with Rails 2.1 or later.
68
+
69
+ == Contributing
70
+
71
+ Contributions are always welcome. Checkout the latest code on GitHub:
72
+ http://github.com/collectiveidea/acts_as_audited
73
+
74
+ Please include tests with your patches. There are a few gems required to run the tests:
75
+ $ gem install multi_rails
76
+ $ gem install thoughtbot-shoulda jnunemaker-matchy --source http://gems.github.com
77
+
78
+ Make sure the tests pass against all versions of Rails since 2.1:
79
+
80
+ $ rake test:multi_rails:all
81
+
82
+ Please report bugs or feature suggestions on GitHub:
83
+ http://github.com/collectiveidea/acts_as_audited/issues
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "acts_as_audited"
8
+ gem.summary = "Acts as audited"
9
+ gem.email = "a@b.com"
10
+ gem.homepage = "http://github.com/andrewzielinski/acts_as_audited"
11
+ gem.authors = ["John Doe"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "acts_as_audited #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,265 @@
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
+ def self.included(base) # :nodoc:
37
+ base.extend ClassMethods
38
+ end
39
+
40
+ module ClassMethods
41
+ # == Configuration options
42
+ #
43
+ #
44
+ # * +only+ - Only audit the given attributes
45
+ # * +except+ - Excludes fields from being saved in the audit log.
46
+ # By default, acts_as_audited will audit all but these fields:
47
+ #
48
+ # [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
49
+ # You can add to those by passing one or an array of fields to skip.
50
+ #
51
+ # class User < ActiveRecord::Base
52
+ # acts_as_audited :except => :password
53
+ # end
54
+ # * +protect+ - If your model uses +attr_protected+, set this to false to prevent Rails from
55
+ # raising an error. If you declare +attr_accessibe+ before calling +acts_as_audited+, it
56
+ # will automatically default to false. You only need to explicitly set this if you are
57
+ # calling +attr_accessible+ after.
58
+ #
59
+ # class User < ActiveRecord::Base
60
+ # acts_as_audited :protect => false
61
+ # attr_accessible :name
62
+ # end
63
+ #
64
+ def acts_as_audited(options = {})
65
+ # don't allow multiple calls
66
+ return if self.included_modules.include?(CollectiveIdea::Acts::Audited::InstanceMethods)
67
+
68
+ options = {:protect => accessible_attributes.nil?}.merge(options)
69
+
70
+ class_inheritable_reader :non_audited_columns
71
+ class_inheritable_reader :auditing_enabled
72
+
73
+ if options[:only]
74
+ except = self.column_names - options[:only].flatten.map(&:to_s)
75
+ else
76
+ except = [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
77
+ except |= Array(options[:except]).collect(&:to_s) if options[:except]
78
+ end
79
+ write_inheritable_attribute :non_audited_columns, except
80
+
81
+ has_many :audits, :as => :auditable, :order => "#{Audit.quoted_table_name}.version"
82
+ attr_protected :audit_ids if options[:protect]
83
+ Audit.audited_class_names << self.to_s
84
+
85
+ after_create :audit_create_callback
86
+ before_update :audit_update_callback
87
+ after_destroy :audit_destroy_callback
88
+
89
+ attr_accessor :version
90
+
91
+ extend CollectiveIdea::Acts::Audited::SingletonMethods
92
+ include CollectiveIdea::Acts::Audited::InstanceMethods
93
+
94
+ write_inheritable_attribute :auditing_enabled, true
95
+ end
96
+ end
97
+
98
+ module InstanceMethods
99
+
100
+ # Temporarily turns off auditing while saving.
101
+ def save_without_auditing
102
+ without_auditing { save }
103
+ end
104
+
105
+ # Executes the block with the auditing callbacks disabled.
106
+ #
107
+ # @foo.without_auditing do
108
+ # @foo.save
109
+ # end
110
+ #
111
+ def without_auditing(&block)
112
+ self.class.without_auditing(&block)
113
+ end
114
+
115
+ # Gets an array of the revisions available
116
+ #
117
+ # user.revisions.each do |revision|
118
+ # user.name
119
+ # user.version
120
+ # end
121
+ #
122
+ def revisions(from_version = 1)
123
+ audits = self.audits.find(:all, :conditions => ['version >= ?', from_version])
124
+ return [] if audits.empty?
125
+ revision = self.audits.find_by_version(from_version).revision
126
+ Audit.reconstruct_attributes(audits) {|attrs| revision.revision_with(attrs) }
127
+ end
128
+
129
+ # Get a specific revision specified by the version number, or +:previous+
130
+ def revision(version)
131
+ revision_with Audit.reconstruct_attributes(audits_to(version))
132
+ end
133
+
134
+ def revision_at(date_or_time)
135
+ audits = self.audits.find(:all, :conditions => ["created_at <= ?", date_or_time])
136
+ revision_with Audit.reconstruct_attributes(audits) unless audits.empty?
137
+ end
138
+
139
+ def audited_attributes
140
+ attributes.except(*non_audited_columns)
141
+ end
142
+
143
+ protected
144
+
145
+ def revision_with(attributes)
146
+ returning self.dup do |revision|
147
+ revision.send :instance_variable_set, '@attributes', self.attributes_before_type_cast
148
+ revision.attributes = attributes.reject {|attr,_| !respond_to?("#{attr}=") }
149
+
150
+ # Remove any association proxies so that they will be recreated
151
+ # and reference the correct object for this revision. The only way
152
+ # to determine if an instance variable is a proxy object is to
153
+ # see if it responds to certain methods, as it forwards almost
154
+ # everything to its target.
155
+ for ivar in revision.instance_variables
156
+ proxy = revision.instance_variable_get ivar
157
+ if !proxy.nil? and proxy.respond_to? :proxy_respond_to?
158
+ revision.instance_variable_set ivar, nil
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def audited_changes
167
+ changed_attributes.except(*non_audited_columns).inject({}) do |changes,(attr, old_value)|
168
+ changes[attr] = [old_value, self[attr]]
169
+ changes
170
+ end
171
+ end
172
+
173
+ def audits_to(version = nil)
174
+ if version == :previous
175
+ version = if self.version
176
+ self.version - 1
177
+ else
178
+ previous = audits.find(:first, :offset => 1,
179
+ :order => "#{Audit.quoted_table_name}.version DESC")
180
+ previous ? previous.version : 1
181
+ end
182
+ end
183
+ audits.find(:all, :conditions => ['version <= ?', version])
184
+ end
185
+
186
+ def audit_create(user = nil)
187
+ write_audit(:action => 'create', :changes => audited_attributes, :user => user)
188
+ end
189
+
190
+ def audit_update(user = nil)
191
+ unless (changes = audited_changes).empty?
192
+ write_audit(:action => 'update', :changes => changes, :user => user)
193
+ end
194
+ end
195
+
196
+ def audit_destroy(user = nil)
197
+ write_audit(:action => 'destroy', :user => user, :changes => audited_attributes)
198
+ end
199
+
200
+ def write_audit(attrs)
201
+ self.audits.create attrs if auditing_enabled
202
+ end
203
+
204
+ CALLBACKS.each do |attr_name|
205
+ alias_method "#{attr_name}_callback".to_sym, attr_name
206
+ end
207
+
208
+ def empty_callback #:nodoc:
209
+ end
210
+
211
+ end # InstanceMethods
212
+
213
+ module SingletonMethods
214
+ # Returns an array of columns that are audited. See non_audited_columns
215
+ def audited_columns
216
+ self.columns.select { |c| !non_audited_columns.include?(c.name) }
217
+ end
218
+
219
+ # Executes the block with auditing disabled.
220
+ #
221
+ # Foo.without_auditing do
222
+ # @foo.save
223
+ # end
224
+ #
225
+ def without_auditing(&block)
226
+ auditing_was_enabled = auditing_enabled
227
+ disable_auditing
228
+ returning(block.call) { enable_auditing if auditing_was_enabled }
229
+ end
230
+
231
+ def disable_auditing
232
+ write_inheritable_attribute :auditing_enabled, false
233
+ end
234
+
235
+ def enable_auditing
236
+ write_inheritable_attribute :auditing_enabled, true
237
+ end
238
+
239
+ def disable_auditing_callbacks
240
+ class_eval do
241
+ CALLBACKS.each do |attr_name|
242
+ alias_method "#{attr_name}_callback", :empty_callback
243
+ end
244
+ end
245
+ end
246
+
247
+ def enable_auditing_callbacks
248
+ class_eval do
249
+ CALLBACKS.each do |attr_name|
250
+ alias_method "#{attr_name}_callback".to_sym, attr_name
251
+ end
252
+ end
253
+ end
254
+
255
+ # All audit operations during the block are recorded as being
256
+ # made by +user+. This is not model specific, the method is a
257
+ # convenience wrapper around #Audit.as_user.
258
+ def audit_as( user, &block )
259
+ Audit.as_user( user, &block )
260
+ end
261
+
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,350 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ module CollectiveIdea
4
+ module Acts
5
+ class AuditedTest < Test::Unit::TestCase
6
+ should "include instance methods" do
7
+ User.new.should be_kind_of(CollectiveIdea::Acts::Audited::InstanceMethods)
8
+ end
9
+
10
+ should "extend singleton methods" do
11
+ User.should be_kind_of(CollectiveIdea::Acts::Audited::SingletonMethods)
12
+ end
13
+
14
+ ['created_at', 'updated_at', 'lock_version', 'id', 'password'].each do |column|
15
+ should "not audit #{column}" do
16
+ User.non_audited_columns.should include(column)
17
+ end
18
+ end
19
+
20
+ should "not save non-audited columns" do
21
+ create_user.audits.first.changes.keys.any?{|col| ['created_at', 'updated_at', 'password'].include? col}.should be(false)
22
+ end
23
+
24
+ context "on create" do
25
+ setup { @user = create_user }
26
+
27
+ should_change 'Audit.count', :by => 1
28
+
29
+ should 'create associated audit' do
30
+ @user.audits.count.should == 1
31
+ end
32
+
33
+ should "set the action to 'create'" do
34
+ @user.audits.first.action.should == 'create'
35
+ end
36
+
37
+ should "store all the audited attributes" do
38
+ @user.audits.first.changes.should == @user.audited_attributes
39
+ end
40
+ end
41
+
42
+ context "on update" do
43
+ setup do
44
+ @user = create_user(:name => 'Brandon')
45
+ end
46
+
47
+ should "save an audit" do
48
+ lambda { @user.update_attribute(:name, "Someone") }.should change { @user.audits.count }.by(1)
49
+ lambda { @user.update_attribute(:name, "Someone else") }.should change { @user.audits.count }.by(1)
50
+ end
51
+
52
+ should "not save an audit if the record is not changed" do
53
+ lambda { @user.save! }.should_not change { Audit.count }
54
+ end
55
+
56
+ should "set the action to 'update'" do
57
+ @user.update_attributes :name => 'Changed'
58
+ @user.audits.last.action.should == 'update'
59
+ end
60
+
61
+ should "store the changed attributes" do
62
+ @user.update_attributes :name => 'Changed'
63
+ @user.audits.last.changes.should == {'name' => ['Brandon', 'Changed']}
64
+ end
65
+
66
+ # Dirty tracking in Rails 2.0-2.2 had issues with type casting
67
+ if ActiveRecord::VERSION::STRING >= '2.3'
68
+ should "not save an audit if the value doesn't change after type casting" do
69
+ @user.update_attributes! :logins => 0, :activated => true
70
+ lambda { @user.update_attribute :logins, '0' }.should_not change { Audit.count }
71
+ lambda { @user.update_attribute :activated, 1 }.should_not change { Audit.count }
72
+ lambda { @user.update_attribute :activated, '1' }.should_not change { Audit.count }
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ context "on destroy" do
79
+ setup do
80
+ @user = create_user
81
+ end
82
+
83
+ should "save an audit" do
84
+ lambda { @user.destroy }.should change { Audit.count }.by(1)
85
+ @user.audits.size.should == 2
86
+ end
87
+
88
+ should "set the action to 'destroy'" do
89
+ @user.destroy
90
+ @user.audits.last.action.should == 'destroy'
91
+ end
92
+
93
+ should "store all of the audited attributes" do
94
+ @user.destroy
95
+ @user.audits.last.changes.should == @user.audited_attributes
96
+ end
97
+
98
+ should "be able to reconstruct destroyed record without history" do
99
+ @user.audits.delete_all
100
+ @user.destroy
101
+ revision = @user.audits.first.revision
102
+ revision.name.should == @user.name
103
+ end
104
+ end
105
+
106
+ context "dirty tracking" do
107
+ setup do
108
+ @user = create_user
109
+ end
110
+
111
+ should "not be changed when the record is saved" do
112
+ u = User.new(:name => 'Brandon')
113
+ u.changed?.should be(true)
114
+ u.save
115
+ u.changed?.should be(false)
116
+ end
117
+
118
+ should "be changed when an attribute has been changed" do
119
+ @user.name = "Bobby"
120
+ @user.changed?.should be(true)
121
+ @user.name_changed?.should be(true)
122
+ @user.username_changed?.should be(false)
123
+ end
124
+
125
+ # Dirty tracking in Rails 2.0-2.2 had issues with type casting
126
+ if ActiveRecord::VERSION::STRING >= '2.3'
127
+ should "not be changed if the value doesn't change after type casting" do
128
+ @user.update_attributes! :logins => 0, :activated => true
129
+ @user.logins = '0'
130
+ @user.changed?.should be(false)
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ context "revisions" do
137
+ setup do
138
+ @user = create_versions
139
+ end
140
+
141
+ should "be an Array of Users" do
142
+ @user.revisions.should be_kind_of(Array)
143
+ @user.revisions.each {|version| version.should be_kind_of(User) }
144
+ end
145
+
146
+ should "have one revision for a new record" do
147
+ create_user.revisions.size.should == 1
148
+ end
149
+
150
+ should "have one revision for each audit" do
151
+ @user.revisions.size.should == @user.audits.size
152
+ end
153
+
154
+ should "set the attributes for each revision" do
155
+ u = User.create(:name => 'Brandon', :username => 'brandon')
156
+ u.update_attributes :name => 'Foobar'
157
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
158
+
159
+ u.revisions.size.should == 3
160
+
161
+ u.revisions[0].name.should == 'Brandon'
162
+ u.revisions[0].username.should == 'brandon'
163
+
164
+ u.revisions[1].name.should == 'Foobar'
165
+ u.revisions[1].username.should == 'brandon'
166
+
167
+ u.revisions[2].name.should == 'Awesome'
168
+ u.revisions[2].username.should == 'keepers'
169
+ end
170
+
171
+ should "access to only recent revisions" do
172
+ u = User.create(:name => 'Brandon', :username => 'brandon')
173
+ u.update_attributes :name => 'Foobar'
174
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
175
+
176
+ u.revisions(2).size.should == 2
177
+
178
+ u.revisions(2)[0].name.should == 'Foobar'
179
+ u.revisions(2)[0].username.should == 'brandon'
180
+
181
+ u.revisions(2)[1].name.should == 'Awesome'
182
+ u.revisions(2)[1].username.should == 'keepers'
183
+ end
184
+
185
+ should "be empty if no audits exist" do
186
+ @user.audits.delete_all
187
+ @user.revisions.empty?.should be(true)
188
+ end
189
+
190
+ should "ignore attributes that have been deleted" do
191
+ @user.audits.last.update_attributes :changes => {:old_attribute => 'old value'}
192
+ lambda { @user.revisions }.should_not raise_error
193
+ end
194
+
195
+ end
196
+
197
+ context "revision" do
198
+ setup do
199
+ @user = create_versions(5)
200
+ end
201
+
202
+ should "maintain identity" do
203
+ @user.revision(1).should == @user
204
+ end
205
+
206
+ should "find the given revision" do
207
+ revision = @user.revision(3)
208
+ revision.should be_kind_of(User)
209
+ revision.version.should == 3
210
+ revision.name.should == 'Foobar 3'
211
+ end
212
+
213
+ should "find the previous revision with :previous" do
214
+ revision = @user.revision(:previous)
215
+ revision.version.should == 4
216
+ revision.should == @user.revision(4)
217
+ end
218
+
219
+ should "be able to get the previous revision repeatedly" do
220
+ previous = @user.revision(:previous)
221
+ previous.version.should == 4
222
+ previous.revision(:previous).version.should == 3
223
+ end
224
+
225
+ should "set the attributes for each revision" do
226
+ u = User.create(:name => 'Brandon', :username => 'brandon')
227
+ u.update_attributes :name => 'Foobar'
228
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
229
+
230
+ u.revision(3).name.should == 'Awesome'
231
+ u.revision(3).username.should == 'keepers'
232
+
233
+ u.revision(2).name.should == 'Foobar'
234
+ u.revision(2).username.should == 'brandon'
235
+
236
+ u.revision(1).name.should == 'Brandon'
237
+ u.revision(1).username.should == 'brandon'
238
+ end
239
+
240
+ should "not raise an error when no previous audits exist" do
241
+ @user.audits.destroy_all
242
+ lambda{ @user.revision(:previous) }.should_not raise_error
243
+ end
244
+
245
+ should "mark revision's attributes as changed" do
246
+ @user.revision(1).name_changed?.should be(true)
247
+ end
248
+
249
+ should "record new audit when saving revision" do
250
+ lambda { @user.revision(1).save! }.should change { @user.audits.count }.by(1)
251
+ end
252
+
253
+ end
254
+
255
+ context "revision_at" do
256
+ should "find the latest revision before the given time" do
257
+ u = create_user
258
+ Audit.update(u.audits.first.id, :created_at => 1.hour.ago)
259
+ u.update_attributes :name => 'updated'
260
+ u.revision_at(2.minutes.ago).version.should == 1
261
+ end
262
+
263
+ should "be nil if given a time before audits" do
264
+ create_user.revision_at(1.week.ago).should be(nil)
265
+ end
266
+
267
+ end
268
+
269
+ context "without auditing" do
270
+
271
+ should "not save an audit when calling #save_without_auditing" do
272
+ lambda {
273
+ u = User.new(:name => 'Brandon')
274
+ u.save_without_auditing.should be(true)
275
+ }.should_not change { Audit.count }
276
+ end
277
+
278
+ should "not save an audit inside of the #without_auditing block" do
279
+ lambda do
280
+ User.without_auditing { User.create(:name => 'Brandon') }
281
+ end.should_not change { Audit.count }
282
+ end
283
+
284
+ should "not save an audit when callbacks are disabled" do
285
+ begin
286
+ User.disable_auditing_callbacks
287
+ lambda { create_user }.should_not change { Audit.count }
288
+ ensure
289
+ User.enable_auditing_callbacks
290
+ end
291
+ end
292
+ end
293
+
294
+ context "attr_protected and attr_accessible" do
295
+ class UnprotectedUser < ActiveRecord::Base
296
+ set_table_name :users
297
+ acts_as_audited :protect => false
298
+ attr_accessible :name, :username, :password
299
+ end
300
+ should "not raise error when attr_accessible is set and protected is false" do
301
+ lambda{
302
+ UnprotectedUser.new(:name => 'NO FAIL!')
303
+ }.should_not raise_error(RuntimeError)
304
+ end
305
+
306
+ class AccessibleUser < ActiveRecord::Base
307
+ set_table_name :users
308
+ attr_accessible :name, :username, :password # declare attr_accessible before calling aaa
309
+ acts_as_audited
310
+ end
311
+ should "not raise an error when attr_accessible is declared before acts_as_audited" do
312
+ lambda{
313
+ AccessibleUser.new(:name => 'NO FAIL!')
314
+ }.should_not raise_error
315
+ end
316
+ end
317
+
318
+ context "audit as" do
319
+ setup do
320
+ @user = User.create :name => 'Testing'
321
+ end
322
+
323
+ should "record user objects" do
324
+ Company.audit_as( @user ) do
325
+ company = Company.create :name => 'The auditors'
326
+ company.name = 'The Auditors'
327
+ company.save
328
+
329
+ company.audits.each do |audit|
330
+ audit.user.should == @user
331
+ end
332
+ end
333
+ end
334
+
335
+ should "record usernames" do
336
+ Company.audit_as( @user.name ) do
337
+ company = Company.create :name => 'The auditors'
338
+ company.name = 'The Auditors, Inc'
339
+ company.save
340
+
341
+ company.audits.each do |audit|
342
+ audit.username.should == @user.name
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,47 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+ require 'rubygems'
4
+ require 'multi_rails_init'
5
+ require 'active_record'
6
+ require 'active_record/version'
7
+ require 'active_record/fixtures'
8
+ require 'action_controller'
9
+ require 'action_controller/test_process'
10
+ require 'action_view'
11
+ require 'test/unit'
12
+ require 'shoulda'
13
+
14
+ gem 'jnunemaker-matchy'
15
+ require 'matchy'
16
+ require File.dirname(__FILE__) + '/../init.rb'
17
+
18
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/db/database.yml'))
19
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
20
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3mem'])
21
+ ActiveRecord::Migration.verbose = false
22
+ load(File.dirname(__FILE__) + "/db/schema.rb")
23
+
24
+ class User < ActiveRecord::Base
25
+ acts_as_audited :except => :password
26
+ end
27
+ class Company < ActiveRecord::Base
28
+ end
29
+
30
+ class Test::Unit::TestCase
31
+ # def change(receiver=nil, message=nil, &block)
32
+ # ChangeExpectation.new(self, receiver, message, &block)
33
+ # end
34
+
35
+ def create_user(attrs = {})
36
+ User.create({:name => 'Brandon', :username => 'brandon', :password => 'password'}.merge(attrs))
37
+ end
38
+
39
+ def create_versions(n = 2)
40
+ returning User.create(:name => 'Foobar 1') do |u|
41
+ (n - 1).times do |i|
42
+ u.update_attribute :name, "Foobar #{i + 2}"
43
+ end
44
+ u.reload
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revo-acts_as_audited
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Keepers
8
+ - Andrew Zielinski
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-07-10 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email: andrew@iplayup.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - LICENSE
25
+ - README.rdoc
26
+ files:
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION
32
+ - lib/acts_as_audited.rb
33
+ - test/acts_as_audited_test.rb
34
+ - test/test_helper.rb
35
+ has_rdoc: false
36
+ homepage: http://github.com/andrewzielinski/acts_as_audited
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --charset=UTF-8
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Acts as audited
61
+ test_files:
62
+ - test/test_helper.rb
63
+ - test/acts_as_audited_test.rb