acts_as_revisionable 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +13 -5
- data/RELEASES.txt +7 -0
- data/Rakefile +4 -3
- data/VERSION +1 -1
- data/acts_as_revisionable.gemspec +13 -15
- data/lib/acts_as_revisionable.rb +101 -30
- data/lib/acts_as_revisionable/revision_record.rb +96 -66
- data/spec/acts_as_revisionable_spec.rb +682 -135
- data/spec/revision_record_spec.rb +291 -254
- data/spec/spec_helper.rb +18 -1
- data/spec/version_1_1_upgrade_spec.rb +41 -0
- metadata +33 -19
- data/spec/full_spec.rb +0 -449
@@ -1,176 +1,723 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ActsAsRevisionable do
|
4
|
-
|
5
|
-
before
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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.
|
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
|
-
|
283
|
+
ActsAsRevisionable::RevisionRecord.count.should == 1
|
99
284
|
end
|
100
|
-
end
|
101
285
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|