revo-acts_as_audited 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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