acts_as_revisionable 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,176 +1,723 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe ActsAsRevisionable do
4
-
5
- before :all do
4
+
5
+ before(:all) do
6
6
  ActsAsRevisionable::Test.create_database
7
+ ActsAsRevisionable::RevisionRecord.create_table
8
+
9
+ ActiveRecord::Base.store_full_sti_class = true
10
+
11
+ class RevisionableTestSubThing < ActiveRecord::Base
12
+ connection.create_table(:revisionable_test_sub_things) do |t|
13
+ t.column :name, :string
14
+ t.column :revisionable_test_many_thing_id, :integer
15
+ end unless table_exists?
16
+ end
17
+
18
+ class RevisionableTestManyThing < ActiveRecord::Base
19
+ connection.create_table(:revisionable_test_many_things) do |t|
20
+ t.column :name, :string
21
+ t.column :revisionable_test_model_id, :integer
22
+ end unless table_exists?
23
+
24
+ has_many :sub_things, :class_name => 'RevisionableTestSubThing'
25
+ end
26
+
27
+ class RevisionableTestManyOtherThing < ActiveRecord::Base
28
+ connection.create_table(:revisionable_test_many_other_things) do |t|
29
+ t.column :name, :string
30
+ t.column :revisionable_test_model_id, :integer
31
+ end unless table_exists?
32
+ end
33
+
34
+ class RevisionableTestCompositeKeyThing < ActiveRecord::Base
35
+ connection.create_table(:revisionable_test_composite_key_things, :id => false) do |t|
36
+ t.column :name, :string
37
+ t.column :revisionable_test_model_id, :integer
38
+ t.column :other_id, :integer
39
+ end unless table_exists?
40
+ set_primary_keys :revisionable_test_model_id, :other_id
41
+ belongs_to :revisionable_test_model
42
+ end
43
+
44
+ class RevisionableTestOneThing < ActiveRecord::Base
45
+ connection.create_table(:revisionable_test_one_things) do |t|
46
+ t.column :name, :string
47
+ t.column :revisionable_test_model_id, :integer
48
+ end unless table_exists?
49
+ end
50
+
51
+ class NonRevisionableTestModel < ActiveRecord::Base
52
+ connection.create_table(:non_revisionable_test_models) do |t|
53
+ t.column :name, :string
54
+ end unless table_exists?
55
+ end
56
+
57
+ class NonRevisionableTestModelsRevisionableTestModel < ActiveRecord::Base
58
+ connection.create_table(:non_revisionable_test_models_revisionable_test_models, :id => false) do |t|
59
+ t.column :non_revisionable_test_model_id, :integer
60
+ t.column :revisionable_test_model_id, :integer
61
+ end unless table_exists?
62
+ end
63
+
64
+ class RevisionableTestModel < ActiveRecord::Base
65
+ connection.create_table(:revisionable_test_models) do |t|
66
+ t.column :name, :string
67
+ t.column :secret, :integer
68
+ end unless table_exists?
69
+
70
+ has_many :many_things, :class_name => 'RevisionableTestManyThing', :dependent => :destroy
71
+ has_many :many_other_things, :class_name => 'RevisionableTestManyOtherThing', :dependent => :destroy
72
+ has_one :one_thing, :class_name => 'RevisionableTestOneThing'
73
+ has_and_belongs_to_many :non_revisionable_test_models
74
+ has_many :composite_key_things, :class_name => 'RevisionableTestCompositeKeyThing', :dependent => :destroy
75
+
76
+ attr_protected :secret
77
+
78
+ acts_as_revisionable :limit => 3, :dependent => :keep, :associations => [:one_thing, :non_revisionable_test_models, {:many_things => :sub_things}, :composite_key_things]
79
+
80
+ def set_secret(val)
81
+ self.secret = val
82
+ end
83
+
84
+ private
85
+
86
+ def secret=(val)
87
+ self[:secret] = val
88
+ end
89
+ end
90
+
91
+ class OtherRevisionableTestModel < ActiveRecord::Base
92
+ connection.create_table(table_name) do |t|
93
+ t.column :name, :string
94
+ t.column :secret, :integer
95
+ end unless table_exists?
96
+
97
+ acts_as_revisionable :on_update => true
98
+ end
99
+
100
+ module ActsAsRevisionable
101
+ class RevisionableNamespaceModel < ActiveRecord::Base
102
+ connection.create_table(:revisionable_namespace_models) do |t|
103
+ t.column :name, :string
104
+ t.column :type_name, :string
105
+ end unless table_exists?
106
+
107
+ set_inheritance_column :type_name
108
+ acts_as_revisionable :dependent => :keep, :on_destroy => true, :encoding => :xml
109
+ self.store_full_sti_class = false
110
+ end
111
+
112
+ class RevisionableSubclassModel < RevisionableNamespaceModel
113
+ end
114
+ end
7
115
  end
8
-
116
+
9
117
  after :all do
10
118
  ActsAsRevisionable::Test.delete_database
11
119
  end
120
+
121
+ before :each do
122
+ RevisionableTestModel.delete_all
123
+ RevisionableTestManyThing.delete_all
124
+ RevisionableTestManyOtherThing.delete_all
125
+ RevisionableTestSubThing.delete_all
126
+ RevisionableTestOneThing.delete_all
127
+ NonRevisionableTestModelsRevisionableTestModel.delete_all
128
+ NonRevisionableTestModel.delete_all
129
+ ActsAsRevisionable::RevisionRecord.delete_all
130
+ ActsAsRevisionable::RevisionableNamespaceModel.delete_all
131
+ OtherRevisionableTestModel.delete_all
132
+ end
133
+
134
+ context "injected methods" do
135
+ it "should be able to inject revisionable behavior onto ActiveRecord::Base" do
136
+ ActiveRecord::Base.included_modules.should include(ActsAsRevisionable)
137
+ end
12
138
 
13
- class TestRevisionableModel
14
- include ActsAsRevisionable
15
-
16
- attr_accessor :id
17
-
18
- def update
19
- really_update
139
+ it "should add as has_many :record_revisions association" do
140
+ RevisionableTestModel.new.revision_records.should == []
20
141
  end
21
-
22
- def really_update
142
+
143
+ it "should parse the revisionable associations" do
144
+ RevisionableTestModel.revisionable_associations.should == {:composite_key_things=>true, :non_revisionable_test_models=>true, :one_thing=>true, :many_things=>{:sub_things=>true}}
23
145
  end
146
+ end
147
+
148
+ context "accessing revisions" do
149
+ let(:record_1){ RevisionableTestModel.create(:name => "record 1") }
150
+ let(:record_2){ OtherRevisionableTestModel.create(:name => "record 2") }
24
151
 
25
- def self.has_many (name, options)
26
- @associations ||= {}
27
- @associations[name] = options
152
+ it "should be able to get a revision for a model" do
153
+ revision_1 = ActsAsRevisionable::RevisionRecord.create(record_1)
154
+ revision_2 = ActsAsRevisionable::RevisionRecord.create(record_1)
155
+ revision_3 = ActsAsRevisionable::RevisionRecord.create(record_2)
156
+ record_1.revision(1).should == revision_1
157
+ record_1.revision(2).should == revision_2
158
+ record_1.revision(3).should == nil
159
+ record_2.revision(1).should == revision_3
28
160
  end
29
161
 
30
- def self.associations
31
- @associations
162
+ it "should be able to get a revision for an id" do
163
+ revision_1 = ActsAsRevisionable::RevisionRecord.create(record_1)
164
+ revision_2 = ActsAsRevisionable::RevisionRecord.create(record_1)
165
+ revision_3 = ActsAsRevisionable::RevisionRecord.create(record_2)
166
+ RevisionableTestModel.revision(record_1.id, 1).should == revision_1
167
+ RevisionableTestModel.revision(record_1.id, 2).should == revision_2
168
+ RevisionableTestModel.revision(record_1.id, 3).should == nil
169
+ OtherRevisionableTestModel.revision(record_2.id, 1).should == revision_3
32
170
  end
33
171
 
34
- private :update
172
+ it "should be able to get the last revision for a model" do
173
+ revision_1 = ActsAsRevisionable::RevisionRecord.create(record_1)
174
+ revision_2 = ActsAsRevisionable::RevisionRecord.create(record_1)
175
+ revision_3 = ActsAsRevisionable::RevisionRecord.create(record_2)
176
+ record_1.last_revision.should == revision_2
177
+ record_2.last_revision.should == revision_3
178
+ end
35
179
 
36
- acts_as_revisionable :limit => 10, :on_update => true, :associations => [:one, {:two => :two_1, :three => [:three_1, :three_2]}, {:four => :four_1}], :encoding => :encoding
37
- end
38
-
39
- it "should be able to inject revisionable behavior onto ActiveRecord::Base" do
40
- ActiveRecord::Base.included_modules.should include(ActsAsRevisionable)
180
+ it "should be able to get the last revision for an id" do
181
+ revision_1 = ActsAsRevisionable::RevisionRecord.create(record_1)
182
+ revision_2 = ActsAsRevisionable::RevisionRecord.create(record_1)
183
+ revision_3 = ActsAsRevisionable::RevisionRecord.create(record_2)
184
+ RevisionableTestModel.last_revision(record_1.id).should == revision_2
185
+ OtherRevisionableTestModel.last_revision(record_2.id).should == revision_3
186
+ end
41
187
  end
42
188
 
43
- it "should add as has_many :record_revisions association" do
44
- TestRevisionableModel.associations[:revision_records].should == {:as => :revisionable, :dependent => :destroy, :order=>"revision DESC", :class_name => "ActsAsRevisionable::RevisionRecord"}
45
- end
189
+ context "storing revisions" do
190
+ it "should not save a revision for a new record" do
191
+ record = RevisionableTestModel.new(:name => "test")
192
+ record.store_revision do
193
+ record.save!
194
+ end
195
+ ActsAsRevisionable::RevisionRecord.count.should == 0
196
+ end
197
+
198
+ it "should only store revisions when a record is updated in a store_revision block" do
199
+ record = RevisionableTestModel.create(:name => "test")
200
+ record.name = "new name"
201
+ record.save!
202
+ ActsAsRevisionable::RevisionRecord.count.should == 0
203
+ record.store_revision do
204
+ record.name = "newer name"
205
+ record.save!
206
+ end
207
+ ActsAsRevisionable::RevisionRecord.count.should == 1
208
+ end
46
209
 
47
- it "should parse the revisionable associations" do
48
- TestRevisionableModel.revisionable_associations.should == {:one => true, :two => {:two_1 => true}, :three => {:three_1 => true, :three_2 => true}, :four => {:four_1 => true}}
49
- end
210
+ it "should always store revisions whenever a record is saved if :on_update is true" do
211
+ record = OtherRevisionableTestModel.create(:name => "test")
212
+ record.name = "new name"
213
+ record.save!
214
+ ActsAsRevisionable::RevisionRecord.count.should == 1
215
+ record.store_revision do
216
+ record.name = "newer name"
217
+ record.save!
218
+ end
219
+ ActsAsRevisionable::RevisionRecord.count.should == 2
220
+ end
50
221
 
51
- it "should handle storing revisions in a block" do
52
- record = TestRevisionableModel.new
53
- record.id = 1
54
- record.stub!(:new_record?).and_return(nil)
55
- end
222
+ it "should only store revisions when a record is destroyed in a store_revision block" do
223
+ record_1 = RevisionableTestModel.create(:name => "test")
224
+ record_1.store_revision do
225
+ record_1.name = "newer name"
226
+ record_1.save!
227
+ end
228
+ record_2 = RevisionableTestModel.create(:name => "test")
229
+ record_2.store_revision do
230
+ record_2.name = "newer name"
231
+ record_2.save!
232
+ end
233
+ ActsAsRevisionable::RevisionRecord.count.should == 2
234
+ record_1.destroy
235
+ ActsAsRevisionable::RevisionRecord.count.should == 2
236
+ record_2.store_revision do
237
+ record_2.destroy
238
+ end
239
+ ActsAsRevisionable::RevisionRecord.count.should == 3
240
+ end
56
241
 
57
- it "should not store revisions for a new record" do
58
- record = TestRevisionableModel.new
59
- record.stub!(:new_record?).and_return(true)
60
- end
242
+ it "should always store revisions whenever a record is destroyed if :on_destroy is true" do
243
+ record_1 = ActsAsRevisionable::RevisionableNamespaceModel.create(:name => "test")
244
+ record_1.store_revision do
245
+ record_1.name = "newer name"
246
+ record_1.save!
247
+ end
248
+ record_2 = ActsAsRevisionable::RevisionableNamespaceModel.create(:name => "test")
249
+ record_2.store_revision do
250
+ record_2.name = "newer name"
251
+ record_2.save!
252
+ end
253
+ ActsAsRevisionable::RevisionRecord.count.should == 2
254
+ record_1.destroy
255
+ ActsAsRevisionable::RevisionRecord.count.should == 3
256
+ record_2.store_revision do
257
+ record_2.destroy
258
+ end
259
+ ActsAsRevisionable::RevisionRecord.count.should == 4
260
+ end
61
261
 
62
- it "should handle storing revisions" do
63
- record = TestRevisionableModel.new
64
- record.id = 1
65
- record.stub!(:new_record?).and_return(nil)
66
- record.stub!(:errors).and_return([])
67
- read_only_record = TestRevisionableModel.new
68
- TestRevisionableModel.should_receive(:find).with(1, :readonly => true).and_return(read_only_record)
69
- revision = mock(:revision)
70
- ActsAsRevisionable::RevisionRecord.should_receive(:transaction).and_yield
71
- read_only_record.should_receive(:create_revision!).and_return(revision)
72
- record.should_receive(:truncate_revisions!).with()
73
- record.should_receive(:really_update)
74
-
75
- record.store_revision do
76
- record.send(:update)
262
+ it "should be able to create a revision record" do
263
+ record_1 = RevisionableTestModel.create(:name => "test")
264
+ ActsAsRevisionable::RevisionRecord.count.should == 0
265
+ record_1.create_revision!
266
+ ActsAsRevisionable::RevisionRecord.count.should == 1
77
267
  end
78
- end
79
268
 
80
- it "should delete a revision if the update fails" do
81
- record = TestRevisionableModel.new
82
- record.id = 1
83
- record.stub!(:new_record?).and_return(nil)
84
- record.stub!(:errors).and_return([])
85
- read_only_record = TestRevisionableModel.new
86
- TestRevisionableModel.should_receive(:find).with(1, :readonly => true).and_return(read_only_record)
87
- revision = mock(:revision)
88
- ActsAsRevisionable::RevisionRecord.should_receive(:transaction).and_yield
89
- read_only_record.should_receive(:create_revision!).and_return(revision)
90
- record.should_receive(:truncate_revisions!).with()
91
- record.should_receive(:really_update).and_raise("update failed")
92
- revision.should_receive(:destroy)
93
-
94
- begin
269
+ it "should not create a revision entry if revisioning is disabled" do
270
+ record = RevisionableTestModel.create(:name => "test")
271
+ ActsAsRevisionable::RevisionRecord.count.should == 0
95
272
  record.store_revision do
96
- record.send(:update)
273
+ record.name = "new name"
274
+ record.save!
275
+ end
276
+ ActsAsRevisionable::RevisionRecord.count.should == 1
277
+ record.disable_revisioning do
278
+ record.store_revision do
279
+ record.name = "newer name"
280
+ record.save!
281
+ end
97
282
  end
98
- rescue
283
+ ActsAsRevisionable::RevisionRecord.count.should == 1
99
284
  end
100
- end
101
285
 
102
- it "should not error on deleting a revision if the update fails" do
103
- record = TestRevisionableModel.new
104
- record.id = 1
105
- record.stub!(:new_record?).and_return(nil)
106
- record.stub!(:errors).and_return([:error])
107
- read_only_record = TestRevisionableModel.new
108
- TestRevisionableModel.should_receive(:find).with(1, :readonly => true).and_return(read_only_record)
109
- revision = mock(:revision)
110
- ActsAsRevisionable::RevisionRecord.should_receive(:transaction).and_yield
111
- read_only_record.should_receive(:create_revision!).and_return(revision)
112
- record.should_receive(:truncate_revisions!).with()
113
- record.should_receive(:update).and_raise("update failed")
114
- revision.should_receive(:destroy).and_raise("destroy failed")
115
-
116
- record.store_revision do
117
- record.send(:update) rescue nil
286
+ it "should truncate the revisions when new ones are created" do
287
+ record = RevisionableTestModel.create(:name => "test")
288
+ 5.times do |i|
289
+ record.store_revision do
290
+ record.update_attribute(:name, "name #{i}")
291
+ end
292
+ end
293
+ ActsAsRevisionable::RevisionRecord.count.should == 3
294
+ record.revision_records.collect{|r| r.revision}.should == [5, 4, 3]
118
295
  end
119
- end
120
296
 
121
- it "should be able to create a revision record" do
122
- record = TestRevisionableModel.new
123
- revision = mock(:revision)
124
- ActsAsRevisionable::RevisionRecord.should_receive(:new).with(record, :encoding).and_return(revision)
125
- revision.should_receive(:save!)
126
- record.create_revision!.should == revision
297
+ it "should not save a revision if an update raises an exception" do
298
+ model = RevisionableTestModel.new(:name => 'test')
299
+ model.store_revision do
300
+ model.save!
301
+ end
302
+ model.reload
303
+ ActsAsRevisionable::RevisionRecord.count.should == 0
304
+
305
+ model.should_receive(:update).and_raise("update failed")
306
+ model.name = 'new_name'
307
+ begin
308
+ model.store_revision do
309
+ ActsAsRevisionable::RevisionRecord.count.should == 1
310
+ model.save
311
+ end
312
+ rescue
313
+ end
314
+ ActsAsRevisionable::RevisionRecord.count.should == 0
315
+ end
316
+
317
+ it "should not save a revision if an update fails with errors" do
318
+ model = RevisionableTestModel.new(:name => 'test')
319
+ model.store_revision do
320
+ model.save!
321
+ end
322
+ model.reload
323
+ ActsAsRevisionable::RevisionRecord.count.should == 0
324
+
325
+ model.name = 'new_name'
326
+ model.store_revision do
327
+ ActsAsRevisionable::RevisionRecord.count.should == 1
328
+ model.save!
329
+ model.errors.add(:name, "isn't right")
330
+ end
331
+ ActsAsRevisionable::RevisionRecord.count.should == 0
332
+ end
333
+
334
+ it "should mark the last revision for a deleted record as being trash" do
335
+ model = ActsAsRevisionable::RevisionableNamespaceModel.new(:name => 'test')
336
+ model.save!
337
+ model.store_revision do
338
+ model.name = "new name"
339
+ model.save!
340
+ end
341
+ model.destroy
342
+ ActsAsRevisionable::RevisionRecord.count.should == 2
343
+ ActsAsRevisionable::RevisionRecord.last_revision(ActsAsRevisionable::RevisionableNamespaceModel, model.id).should be_trash
344
+ end
127
345
  end
346
+
347
+ context "restoring revisions" do
348
+ it "should restore a record without associations" do
349
+ model = RevisionableTestModel.new(:name => 'test')
350
+ model.set_secret(1234)
351
+ model.store_revision do
352
+ model.save!
353
+ end
354
+ model.reload
355
+ ActsAsRevisionable::RevisionRecord.count.should == 0
356
+
357
+ model.name = 'new_name'
358
+ model.set_secret(5678)
359
+ model.store_revision do
360
+ model.save!
361
+ end
362
+ model.reload
363
+ ActsAsRevisionable::RevisionRecord.count.should == 1
364
+ model.name.should == 'new_name'
365
+ model.secret.should == 5678
366
+
367
+ restored = model.restore_revision(1)
368
+ restored.name.should == 'test'
369
+ restored.secret.should == 1234
370
+ restored.id.should == model.id
371
+
372
+ restored.store_revision do
373
+ restored.save!
374
+ end
375
+ RevisionableTestModel.count.should == 1
376
+ ActsAsRevisionable::RevisionRecord.count.should == 2
377
+ restored_model = RevisionableTestModel.find(model.id)
378
+ restored_model.name.should == restored.name
379
+ restored_model.secret.should == restored.secret
380
+ end
381
+
382
+ it "should restore a record with has_many associations" do
383
+ many_thing_1 = RevisionableTestManyThing.new(:name => 'many_thing_1')
384
+ many_thing_1.sub_things.build(:name => 'sub_thing_1')
385
+ many_thing_1.sub_things.build(:name => 'sub_thing_2')
386
+
387
+ model = RevisionableTestModel.new(:name => 'test')
388
+ model.many_things << many_thing_1
389
+ model.many_things.build(:name => 'many_thing_2')
390
+ model.many_other_things.build(:name => 'many_other_thing_1')
391
+ model.many_other_things.build(:name => 'many_other_thing_2')
392
+ model.save!
393
+ model.reload
394
+ RevisionableTestManyThing.count.should == 2
395
+ RevisionableTestSubThing.count.should == 2
396
+ RevisionableTestManyOtherThing.count.should == 2
397
+ ActsAsRevisionable::RevisionRecord.count.should == 0
398
+
399
+ model.store_revision do
400
+ model.name = 'new_name'
401
+ many_thing_1 = model.many_things.detect{|t| t.name == 'many_thing_1'}
402
+ many_thing_1.name = 'new_many_thing_1'
403
+ sub_thing_1 = many_thing_1.sub_things.detect{|t| t.name == 'sub_thing_1'}
404
+ sub_thing_1.name = 'new_sub_thing_1'
405
+ sub_thing_2 = many_thing_1.sub_things.detect{|t| t.name == 'sub_thing_2'}
406
+ many_thing_1.sub_things.build(:name => 'sub_thing_3')
407
+ many_thing_1.sub_things.delete(sub_thing_2)
408
+ many_thing_2 = model.many_things.detect{|t| t.name == 'many_thing_2'}
409
+ model.many_things.delete(many_thing_2)
410
+ model.many_things.build(:name => 'many_thing_3')
411
+ many_other_thing_1 = model.many_other_things.detect{|t| t.name == 'many_other_thing_1'}
412
+ many_other_thing_1.name = 'new_many_other_thing_1'
413
+ many_other_thing_2 = model.many_other_things.detect{|t| t.name == 'many_other_thing_2'}
414
+ model.many_other_things.delete(many_other_thing_2)
415
+ model.many_other_things.build(:name => 'many_other_thing_3')
416
+ model.save!
417
+ many_thing_1.save!
418
+ sub_thing_1.save!
419
+ many_other_thing_1.save!
420
+ end
421
+
422
+ model.reload
423
+ ActsAsRevisionable::RevisionRecord.count.should == 1
424
+ RevisionableTestManyThing.count.should == 2
425
+ RevisionableTestSubThing.count.should == 3
426
+ RevisionableTestManyOtherThing.count.should == 2
427
+ model.name.should == 'new_name'
428
+ model.many_things.collect{|t| t.name}.sort.should == ['many_thing_3', 'new_many_thing_1']
429
+ model.many_things.detect{|t| t.name == 'new_many_thing_1'}.sub_things.collect{|t| t.name}.sort.should == ['new_sub_thing_1', 'sub_thing_3']
430
+ model.many_other_things.collect{|t| t.name}.sort.should == ['many_other_thing_3', 'new_many_other_thing_1']
431
+
432
+ # restore to memory
433
+ restored = model.restore_revision(1)
434
+ restored.name.should == 'test'
435
+ restored.id.should == model.id
436
+ restored.many_things.collect{|t| t.name}.sort.should == ['many_thing_1', 'many_thing_2']
437
+ restored.many_things.detect{|t| t.name == 'many_thing_1'}.sub_things.collect{|t| t.name}.sort.should == ['sub_thing_1', 'sub_thing_2']
438
+ restored.many_other_things.collect{|t| t.name}.sort.should == ['many_other_thing_3', 'new_many_other_thing_1']
439
+ restored.valid?.should == true
440
+
441
+ # make the restore to memory didn't affect the database
442
+ model.reload
443
+ model.name.should == 'new_name'
444
+ model.many_things(true).collect{|t| t.name}.sort.should == ['many_thing_3', 'new_many_thing_1']
445
+ model.many_things.detect{|t| t.name == 'new_many_thing_1'}.sub_things.collect{|t| t.name}.sort.should == ['new_sub_thing_1', 'sub_thing_3']
446
+ model.many_other_things.collect{|t| t.name}.sort.should == ['many_other_thing_3', 'new_many_other_thing_1']
447
+
448
+ model.restore_revision!(1)
449
+ RevisionableTestModel.count.should == 1
450
+ RevisionableTestManyThing.count.should == 2
451
+ RevisionableTestSubThing.count.should == 3
452
+ RevisionableTestManyOtherThing.count.should == 2
453
+ ActsAsRevisionable::RevisionRecord.count.should == 2
454
+ restored_model = RevisionableTestModel.find(model.id)
455
+ restored_model.name.should == 'test'
456
+ restored.many_things.collect{|t| t.name}.sort.should == ['many_thing_1', 'many_thing_2']
457
+ restored.many_things.detect{|t| t.name == 'many_thing_1'}.sub_things.collect{|t| t.name}.sort.should == ['sub_thing_1', 'sub_thing_2']
458
+ restored.many_things.detect{|t| t.name == 'many_thing_2'}.sub_things.collect{|t| t.name}.sort.should == []
459
+ restored.many_other_things.collect{|t| t.name}.sort.should == ['many_other_thing_3', 'new_many_other_thing_1']
460
+ end
461
+
462
+ it "should restore a record with has_one associations" do
463
+ model = RevisionableTestModel.new(:name => 'test')
464
+ model.build_one_thing(:name => 'other')
465
+ model.store_revision do
466
+ model.save!
467
+ end
468
+ model.reload
469
+ ActsAsRevisionable::RevisionRecord.count.should == 0
470
+ RevisionableTestOneThing.count.should == 1
128
471
 
129
- it "should create a revision entry when a model is updated if :on_update is true" do
130
- record = TestRevisionableModel.new
131
- record.should_receive(:new_record?).and_return(false)
132
- record.should_receive(:errors).and_return([])
133
- record.should_receive(:really_update).and_return(:retval)
134
- record.send(:update).should == :retval
135
- end
472
+ model.name = 'new_name'
473
+ model.one_thing.name = 'new_other'
474
+ model.store_revision do
475
+ model.one_thing.save!
476
+ model.save!
477
+ end
136
478
 
137
- it "should not create a revision entry if revisioning is disabled" do
138
- record = TestRevisionableModel.new
139
- record.stub!(:new_record?).and_return(nil)
140
- TestRevisionableModel.should_not_receive(:find)
141
- ActsAsRevisionable::RevisionRecord.should_not_receive(:transaction)
142
- record.should_not_receive(:create_revision!)
143
- record.should_not_receive(:truncate_revisions!)
144
- record.should_receive(:update)
145
-
146
- record.disable_revisioning do
147
- record.store_revision do
148
- record.send(:update)
479
+ model.reload
480
+ ActsAsRevisionable::RevisionRecord.count.should == 1
481
+ model.name.should == 'new_name'
482
+ model.one_thing.name.should == 'new_other'
483
+
484
+ # restore to memory
485
+ restored = model.restore_revision(1)
486
+ restored.name.should == 'test'
487
+ restored.one_thing.name.should == 'other'
488
+ restored.one_thing.id.should == model.one_thing.id
489
+
490
+ # make sure restore to memory didn't affect the database
491
+ model.reload
492
+ ActsAsRevisionable::RevisionRecord.count.should == 1
493
+ model.name.should == 'new_name'
494
+ model.one_thing(true).name.should == 'new_other'
495
+
496
+ model.restore_revision!(1)
497
+ RevisionableTestModel.count.should == 1
498
+ RevisionableTestOneThing.count.should == 1
499
+ ActsAsRevisionable::RevisionRecord.count.should == 2
500
+ restored_model = RevisionableTestModel.find(model.id)
501
+ restored_model.name.should == 'test'
502
+ restored_model.one_thing.name.should == 'other'
503
+ restored_model.one_thing.id.should == model.one_thing.id
504
+ end
505
+
506
+ it "should restore a record with has_and_belongs_to_many associations" do
507
+ other_1 = NonRevisionableTestModel.create(:name => 'one')
508
+ other_2 = NonRevisionableTestModel.create(:name => 'two')
509
+ model = RevisionableTestModel.new(:name => 'test')
510
+ model.non_revisionable_test_models = [other_1, other_2]
511
+ model.store_revision do
512
+ model.save!
513
+ end
514
+ model.reload
515
+ ActsAsRevisionable::RevisionRecord.count.should == 0
516
+ NonRevisionableTestModel.count.should == 2
517
+
518
+ model.name = 'new_name'
519
+ other_1.name = '111'
520
+ other_3 = NonRevisionableTestModel.create(:name => '333')
521
+ model.store_revision do
522
+ model.non_revisionable_test_models = [other_1, other_3]
523
+ other_1.save!
524
+ model.save!
525
+ end
526
+
527
+ model.reload
528
+ ActsAsRevisionable::RevisionRecord.count.should == 1
529
+ NonRevisionableTestModel.count.should == 3
530
+ model.name.should == 'new_name'
531
+ model.non_revisionable_test_models.collect{|r| r.name}.sort.should == ['111', '333']
532
+
533
+ # restore to memory
534
+ restored = model.restore_revision(1)
535
+ restored.name.should == 'test'
536
+ restored.non_revisionable_test_models.collect{|r| r.name}.sort.should == ['111', 'two']
537
+
538
+ # make sure the restore to memory didn't affect the database
539
+ model.reload
540
+ model.name.should == 'new_name'
541
+ model.non_revisionable_test_models(true).collect{|r| r.name}.sort.should == ['111', '333']
542
+
543
+ model.restore_revision!(1)
544
+ NonRevisionableTestModelsRevisionableTestModel.count.should == 2
545
+ RevisionableTestModel.count.should == 1
546
+ NonRevisionableTestModel.count.should == 3
547
+ ActsAsRevisionable::RevisionRecord.count.should == 2
548
+ restored_model = RevisionableTestModel.find(model.id)
549
+ restored_model.name.should == 'test'
550
+ restored_model.non_revisionable_test_models.collect{|r| r.name}.sort.should == ['111', 'two']
551
+ end
552
+
553
+ it "should handle namespaces and single table inheritance" do
554
+ model = ActsAsRevisionable::RevisionableNamespaceModel.new(:name => 'test')
555
+ model.store_revision do
556
+ model.save!
557
+ end
558
+ model.reload
559
+ ActsAsRevisionable::RevisionRecord.count.should == 0
560
+
561
+ model.name = 'new_name'
562
+ model.store_revision do
563
+ model.save!
149
564
  end
565
+ model.reload
566
+ ActsAsRevisionable::RevisionRecord.count.should == 1
567
+ model.name.should == 'new_name'
568
+
569
+ restored = model.restore_revision(1)
570
+ restored.class.should == ActsAsRevisionable::RevisionableNamespaceModel
571
+ restored.name.should == 'test'
572
+ restored.id.should == model.id
150
573
  end
151
- end
152
574
 
153
- it "should truncate the revisions" do
154
- record = TestRevisionableModel.new
155
- record.stub!(:id).and_return(1)
156
- ActsAsRevisionable::RevisionRecord.should_receive(:truncate_revisions).with(TestRevisionableModel, 1, {:limit => 20, :minimum_age => 2.weeks})
157
- record.truncate_revisions!(:limit => 20, :minimum_age => 2.weeks)
158
- end
575
+ it "should handle single table inheritance" do
576
+ model = ActsAsRevisionable::RevisionableSubclassModel.new(:name => 'test')
577
+ model.store_revision do
578
+ model.save!
579
+ end
580
+ model.reload
581
+ ActsAsRevisionable::RevisionRecord.count.should == 0
159
582
 
160
- it "should be able to restore a revision by id and revision" do
161
- revision = mock(:revision)
162
- record = mock(:record)
163
- ActsAsRevisionable::RevisionRecord.should_receive(:find_revision).with(TestRevisionableModel, 1, 5).and_return(revision)
164
- revision.should_receive(:restore).and_return(record)
165
- TestRevisionableModel.restore_revision(1, 5).should == record
166
- end
583
+ model.name = 'new_name'
584
+ model.store_revision do
585
+ model.save!
586
+ end
587
+ model.reload
588
+ ActsAsRevisionable::RevisionRecord.count.should == 1
589
+ model.name.should == 'new_name'
590
+
591
+ restored = model.restore_revision(1)
592
+ restored.class.should == ActsAsRevisionable::RevisionableSubclassModel
593
+ restored.name.should == 'test'
594
+ restored.id.should == model.id
595
+ restored.type_name.should == 'RevisionableSubclassModel'
596
+ end
597
+
598
+ it "should handle composite primary keys" do
599
+ thing_1 = RevisionableTestCompositeKeyThing.new(:name => 'thing_1')
600
+ thing_1.other_id = 1
601
+ thing_2 = RevisionableTestCompositeKeyThing.new(:name => 'thing_2')
602
+ thing_2.other_id = 2
603
+ thing_3 = RevisionableTestCompositeKeyThing.new(:name => 'thing_3')
604
+ thing_3.other_id = 3
605
+
606
+ model = RevisionableTestModel.new(:name => 'test')
607
+ model.composite_key_things << thing_1
608
+ model.composite_key_things << thing_2
609
+ model.save!
610
+ model.reload
611
+ RevisionableTestCompositeKeyThing.count.should == 2
612
+ ActsAsRevisionable::RevisionRecord.count.should == 0
613
+
614
+ model.store_revision do
615
+ thing_1 = model.composite_key_things.detect{|t| t.name == 'thing_1'}
616
+ thing_1.name = 'new_thing_1'
617
+ thing_2 = model.composite_key_things.detect{|t| t.name == 'thing_2'}
618
+ model.composite_key_things.delete(thing_2)
619
+ model.composite_key_things << thing_3
620
+ model.save!
621
+ thing_1.save!
622
+ end
623
+
624
+ model.reload
625
+ ActsAsRevisionable::RevisionRecord.count.should == 1
626
+ RevisionableTestCompositeKeyThing.count.should == 2
627
+ model.composite_key_things.collect{|t| t.name}.sort.should == ['new_thing_1', 'thing_3']
628
+
629
+ # restore to memory
630
+ restored = model.restore_revision(1)
631
+ restored.composite_key_things.collect{|t| t.name}.sort.should == ['thing_1', 'thing_2']
632
+ restored.valid?.should == true
633
+
634
+ # make sure the restore to memory didn't affect the database
635
+ model.reload
636
+ model.composite_key_things(true).collect{|t| t.name}.sort.should == ['new_thing_1', 'thing_3']
637
+ RevisionableTestCompositeKeyThing.count.should == 2
167
638
 
168
- it "should be able to restore a revision by id and revision and save it" do
169
- record = mock(:record)
170
- TestRevisionableModel.should_receive(:restore_revision).with(1, 5).and_return(record)
171
- record.should_receive(:store_revision).and_yield
172
- TestRevisionableModel.should_receive(:save_restorable_associations).with(record, TestRevisionableModel.revisionable_associations)
173
- TestRevisionableModel.restore_revision!(1, 5).should == record
639
+ model.restore_revision!(1)
640
+ RevisionableTestModel.count.should == 1
641
+ RevisionableTestCompositeKeyThing.count.should == 3
642
+ restored_model = RevisionableTestModel.find(model.id)
643
+ restored_model.name.should == 'test'
644
+ restored.composite_key_things.collect{|t| t.name}.sort.should == ['thing_1', 'thing_2']
645
+ end
646
+
647
+ it "should restore a deleted record" do
648
+ model = ActsAsRevisionable::RevisionableNamespaceModel.new(:name => 'test')
649
+ model.save!
650
+ model.store_revision do
651
+ model.name = "new name"
652
+ model.save!
653
+ end
654
+ model.destroy
655
+ ActsAsRevisionable::RevisionRecord.count.should == 2
656
+ ActsAsRevisionable::RevisionableNamespaceModel.restore_last_revision!(model.id)
657
+ end
174
658
  end
175
659
 
660
+ context "cleaning up revisions" do
661
+ it "should destroy revisions if :dependent => :keep was not specified" do
662
+ model = OtherRevisionableTestModel.create(:name => 'test')
663
+ ActsAsRevisionable::RevisionRecord.count.should == 0
664
+
665
+ model.name = 'new_name'
666
+ model.store_revision do
667
+ model.save!
668
+ end
669
+ model.reload
670
+ ActsAsRevisionable::RevisionRecord.count.should == 1
671
+ model.name.should == 'new_name'
672
+
673
+ model.destroy
674
+ ActsAsRevisionable::RevisionRecord.count.should == 0
675
+ end
676
+
677
+ it "should not destroy revisions if :dependent => :keep was specified" do
678
+ model = ActsAsRevisionable::RevisionableSubclassModel.new(:name => 'test')
679
+ model.store_revision do
680
+ model.save!
681
+ end
682
+ model.reload
683
+ ActsAsRevisionable::RevisionRecord.count.should == 0
684
+
685
+ model.name = 'new_name'
686
+ model.store_revision do
687
+ model.save!
688
+ end
689
+ model.reload
690
+ ActsAsRevisionable::RevisionRecord.count.should == 1
691
+ model.name.should == 'new_name'
692
+
693
+ # Destroy adds a revision in this model
694
+ model.destroy
695
+ ActsAsRevisionable::RevisionRecord.count.should == 2
696
+ end
697
+
698
+ it "should empty the trash by deleting all revisions for records that have been deleted for a specified period" do
699
+ record_1 = ActsAsRevisionable::RevisionableNamespaceModel.create(:name => 'test')
700
+ record_1.store_revision do
701
+ record_1.update_attribute(:name, "new")
702
+ end
703
+ record_1.store_revision do
704
+ record_1.update_attribute(:name, "newer")
705
+ end
706
+ record_1.store_revision do
707
+ record_1.destroy
708
+ end
709
+ record_2 = ActsAsRevisionable::RevisionableNamespaceModel.create(:name => 'test 2')
710
+ record_2.store_revision do
711
+ record_2.update_attribute(:name, "new 2")
712
+ end
713
+
714
+ now = Time.now
715
+ ActsAsRevisionable::RevisionRecord.count.should == 4
716
+ ActsAsRevisionable::RevisionableNamespaceModel.empty_trash(60)
717
+ ActsAsRevisionable::RevisionRecord.count.should == 4
718
+ Time.stub(:now => now + 61)
719
+ ActsAsRevisionable::RevisionableNamespaceModel.empty_trash(60)
720
+ ActsAsRevisionable::RevisionRecord.count.should == 1
721
+ end
722
+ end
176
723
  end