acts_as_audited_rails3 1.1.1.4

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