acts_as_audited_rails3 1.1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG +25 -0
  3. data/LICENSE +19 -0
  4. data/README +76 -0
  5. data/Rakefile +60 -0
  6. data/VERSION +1 -0
  7. data/acts_as_audited.gemspec +90 -0
  8. data/acts_as_audited_rails3.gemspec +90 -0
  9. data/doc/classes/Audit.html +407 -0
  10. data/doc/classes/CollectiveIdea/Acts/Audited/ClassMethods.html +226 -0
  11. data/doc/classes/CollectiveIdea/Acts/Audited/InstanceMethods.html +330 -0
  12. data/doc/classes/CollectiveIdea/Acts/Audited/SingletonMethods.html +254 -0
  13. data/doc/created.rid +1 -0
  14. data/doc/files/README.html +226 -0
  15. data/doc/files/lib/acts_as_audited/audit_rb.html +108 -0
  16. data/doc/files/lib/acts_as_audited/audit_sweeper_rb.html +101 -0
  17. data/doc/files/lib/acts_as_audited_rb.html +129 -0
  18. data/doc/fr_class_index.html +30 -0
  19. data/doc/fr_file_index.html +30 -0
  20. data/doc/fr_method_index.html +47 -0
  21. data/doc/index.html +24 -0
  22. data/doc/rdoc-style.css +208 -0
  23. data/lib/acts_as_audited/audit.rb +119 -0
  24. data/lib/acts_as_audited/audit_sweeper.rb +37 -0
  25. data/lib/acts_as_audited/base.rb +316 -0
  26. data/lib/acts_as_audited.rb +9 -0
  27. data/lib/generators/audited_migration/USAGE +7 -0
  28. data/lib/generators/audited_migration/audited_migration_generator.rb +24 -0
  29. data/lib/generators/audited_migration/templates/migration.rb +29 -0
  30. data/lib/generators/audited_migration_update/USAGE +7 -0
  31. data/lib/generators/audited_migration_update/audited_migration_update_generator.rb +24 -0
  32. data/lib/generators/audited_migration_update/templates/migration.rb +9 -0
  33. data/test/acts_as_audited_test.rb +437 -0
  34. data/test/audit_sweeper_test.rb +31 -0
  35. data/test/audit_test.rb +179 -0
  36. data/test/db/database.yml +21 -0
  37. data/test/db/schema.rb +33 -0
  38. data/test/test_helper.rb +75 -0
  39. metadata +152 -0
@@ -0,0 +1,9 @@
1
+ require 'acts_as_audited/base'
2
+ require 'acts_as_audited/audit'
3
+ require 'acts_as_audited'
4
+
5
+ ActiveRecord::Base.send :include, CollectiveIdea::Acts::Audited
6
+
7
+ if defined?(ActionController) and defined?(ActionController::Base)
8
+ require 'acts_as_audited/audit_sweeper'
9
+ 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,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class AuditedMigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ # Implement the required interface for Rails::Generators::Migration.
12
+ # taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
13
+ def self.next_migration_number(dirname) #:nodoc:
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_acts_as_audited_migration
22
+ migration_template 'migration.rb', 'db/migrate/acts_as_audited_migration.rb'
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ class ActsAsAuditedMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits, :force => true do |t|
4
+ t.column :created_at, :datetime
5
+ t.column :updated_at, :datetime
6
+ t.column :auditable_id, :integer
7
+ t.column :auditable_type, :string
8
+ t.column :user_id, :integer
9
+ t.column :user_type, :string
10
+ t.column :username, :string
11
+ t.column :action, :string
12
+ t.column :audit_changes, :text
13
+ t.column :version, :integer, :default => 0
14
+ t.column :comment, :string
15
+ end
16
+ if connection.adapter_name =~ /mysql/i
17
+ execute 'ALTER TABLE audits ADD COLUMN full_model longtext collate utf8_unicode_ci'
18
+ else
19
+ add_column :audits, :full_model, :text
20
+ end
21
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
22
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
23
+ add_index :audits, :created_at
24
+ end
25
+
26
+ def self.down
27
+ drop_table :audits
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ Description:
2
+ The audited migration generator creates a migration to update the audits table to allow for audit comments. It should only be used if upgrading acts_as_audited from a versino which does not permit audit comments
3
+
4
+ Example:
5
+ ./script/generate audited_migration_upgrade update_audits_table
6
+
7
+ This will create a migration in db/migrate/. Run "rake db:migrate" to update your database.
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class AuditedMigrationUpdateGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ # Implement the required interface for Rails::Generators::Migration.
12
+ # taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
13
+ def self.next_migration_number(dirname) #:nodoc:
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_acts_as_audited_update_migration
22
+ migration_template 'migration.rb', 'db/migrate/acts_as_audited_update_migration.rb'
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ class ActsAsAuditedUpdateMigration < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :audits, :comment, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :audits, :comment
8
+ end
9
+ end
@@ -0,0 +1,437 @@
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', 'created_on', 'updated_on', '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.audit_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 :audit_comment => "Create" }
26
+
27
+ should_change 'Audit.count', :by => 1
28
+
29
+ should 'create associated audit' do
30
+ @user.audits.count.should == 1
31
+ end
32
+ should "set the action to 'create'" do
33
+ @user.audits.first.action.should == 'create'
34
+ end
35
+
36
+ should "store all the audited attributes" do
37
+ @user.audits.first.audit_changes.should == @user.audited_attributes
38
+ end
39
+
40
+
41
+ should "not audit an attribute which is excepted if specified on create and on destroy" do
42
+ on_create_destroy_except_name = OnCreateDestroyExceptName.create(:name => 'Bart')
43
+ on_create_destroy_except_name.audits.first.audit_changes.keys.any?{|col| ['name'].include? col}.should be(false)
44
+ end
45
+
46
+
47
+ should "store comment" do
48
+ @user.audits.first.comment.should == "Create"
49
+ end
50
+
51
+
52
+ should "not save an audit if only specified on update and on destroy" do
53
+ lambda { on_update_destroy = OnUpdateDestroy.create(:name => 'Bart') }.should_not change { Audit.count }
54
+ end
55
+ end
56
+
57
+ context "on update" do
58
+ setup do
59
+ @user = create_user(:name => 'Brandon', :audit_comment => "Update")
60
+ end
61
+
62
+ should "save an audit" do
63
+ lambda { @user.update_attribute(:name, "Someone") }.should change { @user.audits.count }.by(1)
64
+ lambda { @user.update_attribute(:name, "Someone else") }.should change { @user.audits.count }.by(1)
65
+ end
66
+
67
+ should "not save an audit if the record is not changed" do
68
+ lambda { @user.save! }.should_not change { Audit.count }
69
+ end
70
+
71
+ should "set the action to 'update'" do
72
+ @user.update_attributes :name => 'Changed'
73
+ @user.audits.last.action.should == 'update'
74
+ end
75
+
76
+ should "store the changed attributes" do
77
+ @user.update_attributes :name => 'Changed'
78
+ @user.audits.last.audit_changes.should == {'name' => ['Brandon', 'Changed']}
79
+ end
80
+
81
+ should "store audit comment" do
82
+ @user.audits.last.comment.should == "Update"
83
+ end
84
+
85
+ # Dirty tracking in Rails 2.0-2.2 had issues with type casting
86
+ if ActiveRecord::VERSION::STRING >= '2.3'
87
+ should "not save an audit if the value doesn't change after type casting" do
88
+ @user.update_attributes! :logins => 0, :activated => true
89
+ lambda { @user.update_attribute :logins, '0' }.should_not change { Audit.count }
90
+ lambda { @user.update_attribute :activated, 1 }.should_not change { Audit.count }
91
+ lambda { @user.update_attribute :activated, '1' }.should_not change { Audit.count }
92
+ end
93
+ end
94
+
95
+ should "not save an audit if only specified on create and on destroy" do
96
+ on_create_destroy = OnCreateDestroy.create(:name => 'Bart')
97
+ lambda { on_create_destroy.update_attributes :name => 'Changed' }.should_not change { Audit.count }
98
+ end
99
+ end
100
+
101
+ context "on destroy" do
102
+ setup do
103
+ @user = create_user
104
+ end
105
+
106
+ should "save an audit" do
107
+ lambda { @user.destroy }.should change { Audit.count }.by(1)
108
+ @user.audits.size.should == 2
109
+ end
110
+
111
+ should "set the action to 'destroy'" do
112
+ @user.destroy
113
+ @user.audits.last.action.should == 'destroy'
114
+ end
115
+
116
+ should "store all of the audited attributes" do
117
+ @user.destroy
118
+ @user.audits.last.audit_changes.should == @user.audited_attributes
119
+ end
120
+
121
+ should "be able to reconstruct destroyed record without history" do
122
+ @user.audits.delete_all
123
+ @user.destroy
124
+ revision = @user.audits.first.revision
125
+ revision.name.should == @user.name
126
+ end
127
+
128
+ should "not save an audit if only specified on create and on update" do
129
+ on_create_update = OnCreateUpdate.create(:name => 'Bart')
130
+ lambda { on_create_update.destroy }.should_not change { Audit.count }
131
+ end
132
+ end
133
+
134
+ context "dirty tracking" do
135
+ setup do
136
+ @user = create_user
137
+ end
138
+
139
+ should "not be changed when the record is saved" do
140
+ u = User.new(:name => 'Brandon')
141
+ u.changed?.should be(true)
142
+ u.save
143
+ u.changed?.should be(false)
144
+ end
145
+
146
+ should "be changed when an attribute has been changed" do
147
+ @user.name = "Bobby"
148
+ @user.changed?.should be(true)
149
+ @user.name_changed?.should be(true)
150
+ @user.username_changed?.should be(false)
151
+ end
152
+
153
+ # Dirty tracking in Rails 2.0-2.2 had issues with type casting
154
+ if ActiveRecord::VERSION::STRING >= '2.3'
155
+ should "not be changed if the value doesn't change after type casting" do
156
+ @user.update_attributes! :logins => 0, :activated => true
157
+ @user.logins = '0'
158
+ @user.changed?.should be(false)
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ context "revisions" do
165
+ setup do
166
+ @user = create_versions
167
+ end
168
+
169
+ should "be an Array of Users" do
170
+ @user.revisions.should be_kind_of(Array)
171
+ @user.revisions.each {|version| version.should be_kind_of(User) }
172
+ end
173
+
174
+ should "have one revision for a new record" do
175
+ create_user.revisions.size.should == 1
176
+ end
177
+
178
+ should "have one revision for each audit" do
179
+ @user.revisions.size.should == @user.audits.size
180
+ end
181
+
182
+ should "set the attributes for each revision" do
183
+ u = User.create(:name => 'Brandon', :username => 'brandon')
184
+ u.update_attributes :name => 'Foobar'
185
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
186
+
187
+ u.revisions.size.should == 3
188
+
189
+ u.revisions[0].name.should == 'Brandon'
190
+ u.revisions[0].username.should == 'brandon'
191
+
192
+ u.revisions[1].name.should == 'Foobar'
193
+ u.revisions[1].username.should == 'brandon'
194
+
195
+ u.revisions[2].name.should == 'Awesome'
196
+ u.revisions[2].username.should == 'keepers'
197
+ end
198
+
199
+ should "access to only recent revisions" do
200
+ u = User.create(:name => 'Brandon', :username => 'brandon')
201
+ u.update_attributes :name => 'Foobar'
202
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
203
+
204
+ u.revisions(2).size.should == 2
205
+
206
+ u.revisions(2)[0].name.should == 'Foobar'
207
+ u.revisions(2)[0].username.should == 'brandon'
208
+
209
+ u.revisions(2)[1].name.should == 'Awesome'
210
+ u.revisions(2)[1].username.should == 'keepers'
211
+ end
212
+
213
+ should "be empty if no audits exist" do
214
+ @user.audits.delete_all
215
+ @user.revisions.empty?.should be(true)
216
+ end
217
+
218
+ should "ignore attributes that have been deleted" do
219
+ @user.audits.last.update_attributes :audit_changes => {:old_attribute => 'old value'}
220
+ lambda { @user.revisions }.should_not raise_error
221
+ end
222
+
223
+ end
224
+
225
+ context "revision" do
226
+ setup do
227
+ @user = create_versions(5)
228
+ end
229
+
230
+ should "maintain identity" do
231
+ @user.revision(1).should == @user
232
+ end
233
+
234
+ should "find the given revision" do
235
+ revision = @user.revision(3)
236
+ revision.should be_kind_of(User)
237
+ revision.version.should == 3
238
+ revision.name.should == 'Foobar 3'
239
+ end
240
+
241
+ should "find the previous revision with :previous" do
242
+ revision = @user.revision(:previous)
243
+ revision.version.should == 4
244
+ revision.should == @user.revision(4)
245
+ end
246
+
247
+ should "be able to get the previous revision repeatedly" do
248
+ previous = @user.revision(:previous)
249
+ previous.version.should == 4
250
+ previous.revision(:previous).version.should == 3
251
+ end
252
+
253
+ should "be able to set protected attributes" do
254
+ u = User.create(:name => 'Brandon')
255
+ u.update_attribute :logins, 1
256
+ u.update_attribute :logins, 2
257
+
258
+ u.revision(3).logins.should == 2
259
+ u.revision(2).logins.should == 1
260
+ u.revision(1).logins.should == 0
261
+ end
262
+
263
+ should "set attributes directly" do
264
+ u = User.create(:name => '<Joe>')
265
+ u.revision(1).name.should == '&lt;Joe&gt;'
266
+ end
267
+
268
+ should "set the attributes for each revision" do
269
+ u = User.create(:name => 'Brandon', :username => 'brandon')
270
+ u.update_attributes :name => 'Foobar'
271
+ u.update_attributes :name => 'Awesome', :username => 'keepers'
272
+
273
+ u.revision(3).name.should == 'Awesome'
274
+ u.revision(3).username.should == 'keepers'
275
+
276
+ u.revision(2).name.should == 'Foobar'
277
+ u.revision(2).username.should == 'brandon'
278
+
279
+ u.revision(1).name.should == 'Brandon'
280
+ u.revision(1).username.should == 'brandon'
281
+ end
282
+
283
+ should "be able to get time for first revision" do
284
+ suspended_at = Time.now
285
+ u = User.create(:suspended_at => suspended_at)
286
+ u.revision(1).suspended_at.should == suspended_at
287
+ end
288
+
289
+ should "not raise an error when no previous audits exist" do
290
+ @user.audits.destroy_all
291
+ lambda{ @user.revision(:previous) }.should_not raise_error
292
+ end
293
+
294
+ should "mark revision's attributes as changed" do
295
+ @user.revision(1).name_changed?.should be(true)
296
+ end
297
+
298
+ should "record new audit when saving revision" do
299
+ lambda { @user.revision(1).save! }.should change { @user.audits.count }.by(1)
300
+ end
301
+
302
+ end
303
+
304
+ context "revision_at" do
305
+ should "find the latest revision before the given time" do
306
+ u = create_user
307
+ Audit.update(u.audits.first.id, :created_at => 1.hour.ago)
308
+ u.update_attributes :name => 'updated'
309
+ u.revision_at(2.minutes.ago).version.should == 1
310
+ end
311
+
312
+ should "be nil if given a time before audits" do
313
+ create_user.revision_at(1.week.ago).should be(nil)
314
+ end
315
+
316
+ end
317
+
318
+ context "without auditing" do
319
+
320
+ should "not save an audit when calling #save_without_auditing" do
321
+ lambda {
322
+ u = User.new(:name => 'Brandon')
323
+ u.save_without_auditing.should be(true)
324
+ }.should_not change { Audit.count }
325
+ end
326
+
327
+ should "not save an audit inside of the #without_auditing block" do
328
+ lambda do
329
+ User.without_auditing { User.create(:name => 'Brandon') }
330
+ end.should_not change { Audit.count }
331
+ end
332
+ end
333
+
334
+ context "comment required" do
335
+ class CommentRequiredUser < ActiveRecord::Base
336
+ set_table_name :users
337
+ acts_as_audited :comment_required => true
338
+ end
339
+
340
+ context "on create" do
341
+ should "not validate when audit_comment is not supplied" do
342
+ CommentRequiredUser.new.valid?.should == false
343
+ end
344
+
345
+ should "validate when audit_comment is supplied" do
346
+ CommentRequiredUser.new(:audit_comment => "Create").valid?.should == true
347
+ end
348
+ end
349
+
350
+ context "on update" do
351
+ setup do
352
+ @user = CommentRequiredUser.create(:audit_comment => "Create")
353
+ end
354
+ should "not validate when audit_comment is not supplied" do
355
+ @user.update_attributes(:name => "Test").should == false
356
+ end
357
+
358
+ should "validate when audit_comment is supplied" do
359
+ @user.update_attributes(:name => "foo", :audit_comment => "Update").should == true
360
+ end
361
+
362
+ end
363
+
364
+ context "on destroy" do
365
+ setup do
366
+ @user = CommentRequiredUser.create(:audit_comment => "Create")
367
+ end
368
+
369
+ should "not validate when audit_comment is unset" do
370
+ @user.destroy.should == false
371
+ end
372
+
373
+ should "validate when audit_comment is supplied" do
374
+ @user.audit_comment = "Destroy"
375
+ @user.destroy.should == @user
376
+ end
377
+ end
378
+
379
+ end
380
+
381
+ context "attr_protected and attr_accessible" do
382
+ class UnprotectedUser < ActiveRecord::Base
383
+ set_table_name :users
384
+ acts_as_audited :protect => false
385
+ attr_accessible :name, :username, :password
386
+ end
387
+ should "not raise error when attr_accessible is set and protected is false" do
388
+ lambda{
389
+ UnprotectedUser.new(:name => 'NO FAIL!')
390
+ }.should_not raise_error(RuntimeError)
391
+ end
392
+
393
+ class AccessibleUser < ActiveRecord::Base
394
+ set_table_name :users
395
+ attr_accessible :name, :username, :password # declare attr_accessible before calling aaa
396
+ acts_as_audited
397
+ end
398
+ should "not raise an error when attr_accessible is declared before acts_as_audited" do
399
+ lambda{
400
+ AccessibleUser.new(:name => 'NO FAIL!')
401
+ }.should_not raise_error
402
+ end
403
+ end
404
+
405
+ context "audit as" do
406
+ setup do
407
+ @user = User.create :name => 'Testing'
408
+ end
409
+
410
+ should "record user objects" do
411
+ Company.audit_as( @user ) do
412
+ company = Company.create :name => 'The auditors'
413
+ company.name = 'The Auditors'
414
+ company.save
415
+
416
+ company.audits.each do |audit|
417
+ audit.user.should == @user
418
+ end
419
+ end
420
+ end
421
+
422
+ should "record usernames" do
423
+ Company.audit_as( @user.name ) do
424
+ company = Company.create :name => 'The auditors'
425
+ company.name = 'The Auditors, Inc'
426
+ company.save
427
+
428
+ company.audits.each do |audit|
429
+ audit.username.should == @user.name
430
+ end
431
+ end
432
+ end
433
+ end
434
+
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ class AuditsController < ActionController::Base
4
+ def audit
5
+ @company = Company.create
6
+ render :nothing => true
7
+ end
8
+
9
+ def update_user
10
+ current_user.update_attributes({:password => 'foo'})
11
+ render :nothing => true
12
+ end
13
+
14
+ private
15
+ attr_accessor :current_user
16
+ end
17
+ AuditsController.view_paths = [File.dirname(__FILE__)]
18
+ ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
19
+
20
+ class AuditsControllerTest < ActionController::TestCase
21
+ should "audit user" do
22
+ user = @controller.send(:current_user=, create_user)
23
+ lambda { post :audit }.should change { Audit.count }
24
+ assigns(:company).audits.last.user.should == user
25
+ end
26
+
27
+ should "not save blank audits" do
28
+ user = @controller.send(:current_user=, create_user)
29
+ lambda { post :update_user }.should_not change { Audit.count }
30
+ end
31
+ end