paper_trail 4.0.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +27 -0
  4. data/CONTRIBUTING.md +78 -5
  5. data/README.md +328 -268
  6. data/doc/bug_report_template.rb +65 -0
  7. data/gemfiles/3.0.gemfile +7 -4
  8. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
  9. data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
  10. data/lib/paper_trail.rb +11 -9
  11. data/lib/paper_trail/attributes_serialization.rb +89 -0
  12. data/lib/paper_trail/cleaner.rb +8 -1
  13. data/lib/paper_trail/config.rb +15 -18
  14. data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
  15. data/lib/paper_trail/has_paper_trail.rb +102 -99
  16. data/lib/paper_trail/record_history.rb +59 -0
  17. data/lib/paper_trail/reifier.rb +270 -0
  18. data/lib/paper_trail/version_association_concern.rb +3 -1
  19. data/lib/paper_trail/version_concern.rb +60 -226
  20. data/lib/paper_trail/version_number.rb +2 -2
  21. data/paper_trail.gemspec +7 -10
  22. data/spec/models/animal_spec.rb +17 -0
  23. data/spec/models/callback_modifier_spec.rb +96 -0
  24. data/spec/models/json_version_spec.rb +20 -17
  25. data/spec/paper_trail/config_spec.rb +52 -0
  26. data/spec/spec_helper.rb +6 -0
  27. data/test/dummy/app/models/callback_modifier.rb +45 -0
  28. data/test/dummy/app/models/chapter.rb +9 -0
  29. data/test/dummy/app/models/citation.rb +5 -0
  30. data/test/dummy/app/models/paragraph.rb +5 -0
  31. data/test/dummy/app/models/quotation.rb +5 -0
  32. data/test/dummy/app/models/section.rb +6 -0
  33. data/test/dummy/config/database.postgres.yml +1 -1
  34. data/test/dummy/config/initializers/paper_trail.rb +3 -1
  35. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
  36. data/test/dummy/db/schema.rb +27 -0
  37. data/test/test_helper.rb +36 -0
  38. data/test/unit/associations_test.rb +726 -0
  39. data/test/unit/inheritance_column_test.rb +6 -6
  40. data/test/unit/model_test.rb +62 -594
  41. data/test/unit/protected_attrs_test.rb +3 -2
  42. data/test/unit/version_test.rb +87 -69
  43. metadata +38 -2
@@ -55,6 +55,19 @@ ActiveRecord::Schema.define(version: 20110208155312) do
55
55
  t.boolean "scoped", default: true
56
56
  end
57
57
 
58
+ create_table "callback_modifiers", force: :cascade do |t|
59
+ t.string "some_content"
60
+ t.boolean "deleted", default: false
61
+ end
62
+
63
+ create_table "chapters", force: :cascade do |t|
64
+ t.string "name"
65
+ end
66
+
67
+ create_table "citations", force: :cascade do |t|
68
+ t.integer "quotation_id"
69
+ end
70
+
58
71
  create_table "customers", force: :cascade do |t|
59
72
  t.string "name"
60
73
  end
@@ -109,6 +122,11 @@ ActiveRecord::Schema.define(version: 20110208155312) do
109
122
  t.string "order_date"
110
123
  end
111
124
 
125
+ create_table "paragraphs", force: :cascade do |t|
126
+ t.integer "section_id"
127
+ t.string "name"
128
+ end
129
+
112
130
  create_table "people", force: :cascade do |t|
113
131
  t.string "name"
114
132
  t.string "time_zone"
@@ -136,6 +154,15 @@ ActiveRecord::Schema.define(version: 20110208155312) do
136
154
  t.string "content"
137
155
  end
138
156
 
157
+ create_table "quotations", force: :cascade do |t|
158
+ t.integer "chapter_id"
159
+ end
160
+
161
+ create_table "sections", force: :cascade do |t|
162
+ t.integer "chapter_id"
163
+ t.string "name"
164
+ end
165
+
139
166
  create_table "skippers", force: :cascade do |t|
140
167
  t.string "name"
141
168
  t.datetime "another_timestamp"
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'pry-nav'
3
+ rescue LoadError
4
+ # It's OK, we don't include pry in e.g. gemfiles/3.0.gemfile
5
+ end
6
+
1
7
  ENV["RAILS_ENV"] = "test"
2
8
  ENV["DB"] ||= "sqlite"
3
9
 
@@ -37,6 +43,36 @@ class ActiveSupport::TestCase
37
43
  DatabaseCleaner.clean if using_mysql?
38
44
  Thread.current[:paper_trail] = nil
39
45
  end
46
+
47
+ private
48
+
49
+ def assert_attributes_equal(expected, actual)
50
+ if using_mysql?
51
+ expected, actual = expected.dup, actual.dup
52
+
53
+ # Adjust timestamps for missing fractional seconds precision.
54
+ %w(created_at updated_at).each do |timestamp|
55
+ expected[timestamp] = expected[timestamp].change(:usec => 0)
56
+ actual[timestamp] = actual[timestamp].change(:usec => 0)
57
+ end
58
+ end
59
+
60
+ assert_equal expected, actual
61
+ end
62
+
63
+ def assert_changes_equal(expected, actual)
64
+ if using_mysql?
65
+ expected, actual = expected.dup, actual.dup
66
+
67
+ # Adjust timestamps for missing fractional seconds precision.
68
+ %w(created_at updated_at).each do |timestamp|
69
+ expected[timestamp][1] = expected[timestamp][1].change(:usec => 0)
70
+ actual[timestamp][1] = actual[timestamp][1].change(:usec => 0)
71
+ end
72
+ end
73
+
74
+ assert_equal expected, actual
75
+ end
40
76
  end
41
77
 
42
78
  #
@@ -0,0 +1,726 @@
1
+ require 'test_helper'
2
+ require 'time_travel_helper'
3
+
4
+ class AssociationsTest < ActiveSupport::TestCase
5
+ CHAPTER_NAMES = [
6
+ "Down the Rabbit-Hole",
7
+ "The Pool of Tears",
8
+ "A Caucus-Race and a Long Tale",
9
+ "The Rabbit Sends in a Little Bill",
10
+ "Advice from a Caterpillar",
11
+ "Pig and Pepper",
12
+ "A Mad Tea-Party",
13
+ "The Queen's Croquet-Ground",
14
+ "The Mock Turtle's Story",
15
+ "The Lobster Quadrille",
16
+ "Who Stole the Tarts?",
17
+ "Alice's Evidence"
18
+ ]
19
+
20
+ # These would have been done in test_helper.rb if using_mysql? is true
21
+ unless using_mysql?
22
+ self.use_transactional_fixtures = false
23
+ setup { DatabaseCleaner.start }
24
+ end
25
+
26
+ teardown do
27
+ Timecop.return
28
+ # This would have been done in test_helper.rb if using_mysql? is true
29
+ DatabaseCleaner.clean unless using_mysql?
30
+ end
31
+
32
+ context "a has_one association" do
33
+ setup { @widget = Widget.create :name => 'widget_0' }
34
+
35
+ context 'before the associated was created' do
36
+ setup do
37
+ @widget.update_attributes :name => 'widget_1'
38
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
39
+ end
40
+
41
+ context 'when reified' do
42
+ setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
43
+
44
+ should 'see the associated as it was at the time' do
45
+ assert_nil @widget_0.wotsit
46
+ end
47
+
48
+ should 'not persist changes to the live association' do
49
+ assert_equal @wotsit, @widget.wotsit(true)
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'where the association is created between model versions' do
55
+ setup do
56
+ @wotsit = @widget.create_wotsit :name => 'wotsit_0'
57
+ Timecop.travel 1.second.since
58
+ @widget.update_attributes :name => 'widget_1'
59
+ end
60
+
61
+ context 'when reified' do
62
+ setup { @widget_0 = @widget.versions.last.reify(:has_one => true) }
63
+
64
+ should 'see the associated as it was at the time' do
65
+ assert_equal 'wotsit_0', @widget_0.wotsit.name
66
+ end
67
+
68
+ should 'not persist changes to the live association' do
69
+ assert_equal @wotsit, @widget.wotsit(true)
70
+ end
71
+ end
72
+
73
+ context 'and then the associated is updated between model versions' do
74
+ setup do
75
+ @wotsit.update_attributes :name => 'wotsit_1'
76
+ @wotsit.update_attributes :name => 'wotsit_2'
77
+ Timecop.travel 1.second.since
78
+ @widget.update_attributes :name => 'widget_2'
79
+ @wotsit.update_attributes :name => 'wotsit_3'
80
+ end
81
+
82
+ context 'when reified' do
83
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => true) }
84
+
85
+ should 'see the associated as it was at the time' do
86
+ assert_equal 'wotsit_2', @widget_1.wotsit.name
87
+ end
88
+
89
+ should 'not persist changes to the live association' do
90
+ assert_equal 'wotsit_3', @widget.wotsit(true).name
91
+ end
92
+ end
93
+
94
+ context 'when reified opting out of has_one reification' do
95
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => false) }
96
+
97
+ should 'see the associated as it is live' do
98
+ assert_equal 'wotsit_3', @widget_1.wotsit.name
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'and then the associated is destroyed' do
104
+ setup do
105
+ @wotsit.destroy
106
+ end
107
+
108
+ context 'when reify' do
109
+ setup { @widget_1 = @widget.versions.last.reify(:has_one => true) }
110
+
111
+ should 'see the associated as it was at the time' do
112
+ assert_equal @wotsit, @widget_1.wotsit
113
+ end
114
+
115
+ should 'not persist changes to the live association' do
116
+ assert_nil @widget.wotsit(true)
117
+ end
118
+ end
119
+
120
+ context 'and then the model is updated' do
121
+ setup do
122
+ Timecop.travel 1.second.since
123
+ @widget.update_attributes :name => 'widget_3'
124
+ end
125
+
126
+ context 'when reified' do
127
+ setup { @widget_2 = @widget.versions.last.reify(:has_one => true) }
128
+
129
+ should 'see the associated as it was at the time' do
130
+ assert_nil @widget_2.wotsit
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ context "a has_many association" do
139
+ setup { @customer = Customer.create :name => 'customer_0' }
140
+
141
+ context 'updated before the associated was created' do
142
+ setup do
143
+ @customer.update_attributes! :name => 'customer_1'
144
+ @customer.orders.create! :order_date => Date.today
145
+ end
146
+
147
+ context 'when reified' do
148
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
149
+
150
+ should 'see the associated as it was at the time' do
151
+ assert_equal [], @customer_0.orders
152
+ end
153
+
154
+ should 'not persist changes to the live association' do
155
+ assert_not_equal [], @customer.orders(true)
156
+ end
157
+ end
158
+
159
+ context 'when reified with option mark_for_destruction' do
160
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
161
+
162
+ should 'mark the associated for destruction' do
163
+ assert_equal [true], @customer_0.orders.map(&:marked_for_destruction?)
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'where the association is created between model versions' do
169
+ setup do
170
+ @order = @customer.orders.create! :order_date => 'order_date_0'
171
+ Timecop.travel 1.second.since
172
+ @customer.update_attributes :name => 'customer_1'
173
+ end
174
+
175
+ context 'when reified' do
176
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
177
+
178
+ should 'see the associated as it was at the time' do
179
+ assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
180
+ end
181
+ end
182
+
183
+ context 'and then a nested has_many association is created' do
184
+ setup do
185
+ @order.line_items.create! :product => 'product_0'
186
+ end
187
+
188
+ context 'when reified' do
189
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
190
+
191
+ should 'see the live version of the nested association' do
192
+ assert_equal ['product_0'], @customer_0.orders.first.line_items.map(&:product)
193
+ end
194
+ end
195
+ end
196
+
197
+ context 'and then the associated is updated between model versions' do
198
+ setup do
199
+ @order.update_attributes :order_date => 'order_date_1'
200
+ @order.update_attributes :order_date => 'order_date_2'
201
+ Timecop.travel 1.second.since
202
+ @customer.update_attributes :name => 'customer_2'
203
+ @order.update_attributes :order_date => 'order_date_3'
204
+ end
205
+
206
+ context 'when reified' do
207
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
208
+
209
+ should 'see the associated as it was at the time' do
210
+ assert_equal ['order_date_2'], @customer_1.orders.map(&:order_date)
211
+ end
212
+
213
+ should 'not persist changes to the live association' do
214
+ assert_equal ['order_date_3'], @customer.orders(true).map(&:order_date)
215
+ end
216
+ end
217
+
218
+ context 'when reified opting out of has_many reification' do
219
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => false) }
220
+
221
+ should 'see the associated as it is live' do
222
+ assert_equal ['order_date_3'], @customer_1.orders.map(&:order_date)
223
+ end
224
+ end
225
+
226
+ context 'and then the associated is destroyed' do
227
+ setup do
228
+ @order.destroy
229
+ end
230
+
231
+ context 'when reified' do
232
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
233
+
234
+ should 'see the associated as it was at the time' do
235
+ assert_equal ['order_date_2'], @customer_1.orders.map(&:order_date)
236
+ end
237
+
238
+ should 'not persist changes to the live association' do
239
+ assert_equal [], @customer.orders(true)
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ context 'and then the associated is destroyed' do
246
+ setup do
247
+ @order.destroy
248
+ end
249
+
250
+ context 'when reified' do
251
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
252
+
253
+ should 'see the associated as it was at the time' do
254
+ assert_equal [@order.order_date], @customer_1.orders.map(&:order_date)
255
+ end
256
+
257
+ should 'not persist changes to the live association' do
258
+ assert_equal [], @customer.orders(true)
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'and then the associated is destroyed between model versions' do
264
+ setup do
265
+ @order.destroy
266
+ Timecop.travel 1.second.since
267
+ @customer.update_attributes :name => 'customer_2'
268
+ end
269
+
270
+ context 'when reified' do
271
+ setup { @customer_1 = @customer.versions.last.reify(:has_many => true) }
272
+
273
+ should 'see the associated as it was at the time' do
274
+ assert_equal [], @customer_1.orders
275
+ end
276
+ end
277
+ end
278
+
279
+ context 'and then another association is added' do
280
+ setup do
281
+ @customer.orders.create! :order_date => 'order_date_1'
282
+ end
283
+
284
+ context 'when reified' do
285
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true) }
286
+
287
+ should 'see the associated as it was at the time' do
288
+ assert_equal ['order_date_0'], @customer_0.orders.map(&:order_date)
289
+ end
290
+
291
+ should 'not persist changes to the live association' do
292
+ assert_equal ['order_date_0', 'order_date_1'], @customer.orders(true).map(&:order_date).sort
293
+ end
294
+ end
295
+
296
+ context 'when reified with option mark_for_destruction' do
297
+ setup { @customer_0 = @customer.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
298
+
299
+ should 'mark the newly associated for destruction' do
300
+ assert @customer_0.orders.detect { |o| o.order_date == 'order_date_1'}.marked_for_destruction?
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ context "has_many through associations" do
308
+ context "Books, Authors, and Authorships" do
309
+ setup { @book = Book.create :title => 'book_0' }
310
+
311
+ context 'updated before the associated was created' do
312
+ setup do
313
+ @book.update_attributes! :title => 'book_1'
314
+ @book.authors.create! :name => 'author_0'
315
+ end
316
+
317
+ context 'when reified' do
318
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
319
+
320
+ should 'see the associated as it was at the time' do
321
+ assert_equal [], @book_0.authors
322
+ end
323
+
324
+ should 'not persist changes to the live association' do
325
+ assert_equal ['author_0'], @book.authors(true).map(&:name)
326
+ end
327
+ end
328
+
329
+ context 'when reified with option mark_for_destruction' do
330
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
331
+
332
+ should 'mark the associated for destruction' do
333
+ assert_equal [true], @book_0.authors.map(&:marked_for_destruction?)
334
+ end
335
+
336
+ should 'mark the associated-through for destruction' do
337
+ assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
338
+ end
339
+ end
340
+ end
341
+
342
+ context 'updated before it is associated with an existing one' do
343
+ setup do
344
+ person_existing = Person.create(:name => 'person_existing')
345
+ Timecop.travel 1.second.since
346
+ @book.update_attributes! :title => 'book_1'
347
+ @book.authors << person_existing
348
+ end
349
+
350
+ context 'when reified' do
351
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
352
+
353
+ should 'see the associated as it was at the time' do
354
+ assert_equal [], @book_0.authors
355
+ end
356
+ end
357
+
358
+ context 'when reified with option mark_for_destruction' do
359
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
360
+
361
+ should 'not mark the associated for destruction' do
362
+ assert_equal [false], @book_0.authors.map(&:marked_for_destruction?)
363
+ end
364
+
365
+ should 'mark the associated-through for destruction' do
366
+ assert_equal [true], @book_0.authorships.map(&:marked_for_destruction?)
367
+ end
368
+ end
369
+ end
370
+
371
+ context 'where the association is created between model versions' do
372
+ setup do
373
+ @author = @book.authors.create! :name => 'author_0'
374
+ @person_existing = Person.create(:name => 'person_existing')
375
+ Timecop.travel 1.second.since
376
+ @book.update_attributes! :title => 'book_1'
377
+ end
378
+
379
+ context 'when reified' do
380
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
381
+
382
+ should 'see the associated as it was at the time' do
383
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
384
+ end
385
+ end
386
+
387
+ context 'and then the associated is updated between model versions' do
388
+ setup do
389
+ @author.update_attributes :name => 'author_1'
390
+ @author.update_attributes :name => 'author_2'
391
+ Timecop.travel 1.second.since
392
+ @book.update_attributes :title => 'book_2'
393
+ @author.update_attributes :name => 'author_3'
394
+ end
395
+
396
+ context 'when reified' do
397
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
398
+
399
+ should 'see the associated as it was at the time' do
400
+ assert_equal ['author_2'], @book_1.authors.map(&:name)
401
+ end
402
+
403
+ should 'not persist changes to the live association' do
404
+ assert_equal ['author_3'], @book.authors(true).map(&:name)
405
+ end
406
+ end
407
+
408
+ context 'when reified opting out of has_many reification' do
409
+ setup { @book_1 = @book.versions.last.reify(:has_many => false) }
410
+
411
+ should 'see the associated as it is live' do
412
+ assert_equal ['author_3'], @book_1.authors.map(&:name)
413
+ end
414
+ end
415
+ end
416
+
417
+ context 'and then the associated is destroyed' do
418
+ setup do
419
+ @author.destroy
420
+ end
421
+
422
+ context 'when reified' do
423
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
424
+
425
+ should 'see the associated as it was at the time' do
426
+ assert_equal [@author.name], @book_1.authors.map(&:name)
427
+ end
428
+
429
+ should 'not persist changes to the live association' do
430
+ assert_equal [], @book.authors(true)
431
+ end
432
+ end
433
+ end
434
+
435
+ context 'and then the associated is destroyed between model versions' do
436
+ setup do
437
+ @author.destroy
438
+ Timecop.travel 1.second.since
439
+ @book.update_attributes :title => 'book_2'
440
+ end
441
+
442
+ context 'when reified' do
443
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
444
+
445
+ should 'see the associated as it was at the time' do
446
+ assert_equal [], @book_1.authors
447
+ end
448
+ end
449
+ end
450
+
451
+ context 'and then the associated is dissociated between model versions' do
452
+ setup do
453
+ @book.authors = []
454
+ Timecop.travel 1.second.since
455
+ @book.update_attributes :title => 'book_2'
456
+ end
457
+
458
+ context 'when reified' do
459
+ setup { @book_1 = @book.versions.last.reify(:has_many => true) }
460
+
461
+ should 'see the associated as it was at the time' do
462
+ assert_equal [], @book_1.authors
463
+ end
464
+ end
465
+ end
466
+
467
+ context 'and then another associated is created' do
468
+ setup do
469
+ @book.authors.create! :name => 'author_1'
470
+ end
471
+
472
+ context 'when reified' do
473
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
474
+
475
+ should 'only see the first associated' do
476
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
477
+ end
478
+
479
+ should 'not persist changes to the live association' do
480
+ assert_equal ['author_0', 'author_1'], @book.authors(true).map(&:name)
481
+ end
482
+ end
483
+
484
+ context 'when reified with option mark_for_destruction' do
485
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
486
+
487
+ should 'mark the newly associated for destruction' do
488
+ assert @book_0.authors.detect { |a| a.name == 'author_1' }.marked_for_destruction?
489
+ end
490
+
491
+ should 'mark the newly associated-through for destruction' do
492
+ assert @book_0.authorships.detect { |as| as.person.name == 'author_1' }.marked_for_destruction?
493
+ end
494
+ end
495
+ end
496
+
497
+ context 'and then an existing one is associated' do
498
+ setup do
499
+ @book.authors << @person_existing
500
+ end
501
+
502
+ context 'when reified' do
503
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
504
+
505
+ should 'only see the first associated' do
506
+ assert_equal ['author_0'], @book_0.authors.map(&:name)
507
+ end
508
+
509
+ should 'not persist changes to the live association' do
510
+ assert_equal ['author_0', 'person_existing'], @book.authors(true).map(&:name).sort
511
+ end
512
+ end
513
+
514
+ context 'when reified with option mark_for_destruction' do
515
+ setup { @book_0 = @book.versions.last.reify(:has_many => true, :mark_for_destruction => true) }
516
+
517
+ should 'not mark the newly associated for destruction' do
518
+ assert !@book_0.authors.detect { |a| a.name == 'person_existing' }.marked_for_destruction?
519
+ end
520
+
521
+ should 'mark the newly associated-through for destruction' do
522
+ assert @book_0.authorships.detect { |as| as.person.name == 'person_existing' }.marked_for_destruction?
523
+ end
524
+ end
525
+ end
526
+ end
527
+
528
+ context 'updated before the associated without paper_trail was created' do
529
+ setup do
530
+ @book.update_attributes! :title => 'book_1'
531
+ @book.editors.create! :name => 'editor_0'
532
+ end
533
+
534
+ context 'when reified' do
535
+ setup { @book_0 = @book.versions.last.reify(:has_many => true) }
536
+
537
+ should 'see the live association' do
538
+ assert_equal ['editor_0'], @book_0.editors.map(&:name)
539
+ end
540
+ end
541
+ end
542
+ end
543
+
544
+ context "Chapters, Sections, Paragraphs, Quotations, and Citations" do
545
+ setup { @chapter = Chapter.create(:name => CHAPTER_NAMES[0]) }
546
+
547
+ context "before any associations are created" do
548
+ setup do
549
+ @chapter.update_attributes(:name => CHAPTER_NAMES[1])
550
+ end
551
+
552
+ should "not reify any associations" do
553
+ chapter_v1 = @chapter.versions[1].reify(:has_many => true)
554
+ assert_equal CHAPTER_NAMES[0], chapter_v1.name
555
+ assert_equal [], chapter_v1.sections
556
+ assert_equal [], chapter_v1.paragraphs
557
+ end
558
+ end
559
+
560
+ context "after the first has_many through relationship is created" do
561
+ setup do
562
+ assert_equal 1, @chapter.versions.size
563
+ @chapter.update_attributes :name => CHAPTER_NAMES[1]
564
+ assert_equal 2, @chapter.versions.size
565
+
566
+ Timecop.travel 1.second.since
567
+ @chapter.sections.create :name => "section 1"
568
+ Timecop.travel 1.second.since
569
+ @chapter.sections.first.update_attributes :name => "section 2"
570
+ Timecop.travel 1.second.since
571
+ @chapter.update_attributes :name => CHAPTER_NAMES[2]
572
+ assert_equal 3, @chapter.versions.size
573
+
574
+ Timecop.travel 1.second.since
575
+ @chapter.sections.first.update_attributes :name => "section 3"
576
+ end
577
+
578
+ context "version 1" do
579
+ should "have no sections" do
580
+ chapter_v1 = @chapter.versions[1].reify(:has_many => true)
581
+ assert_equal [], chapter_v1.sections
582
+ end
583
+ end
584
+
585
+ context "version 2" do
586
+ should "have one section" do
587
+ chapter_v2 = @chapter.versions[2].reify(:has_many => true)
588
+ assert_equal 1, chapter_v2.sections.size
589
+
590
+ # Shows the value of the section as it was before
591
+ # the chapter was updated.
592
+ assert_equal ['section 2'], chapter_v2.sections.map(&:name)
593
+
594
+ # Shows the value of the chapter as it was before
595
+ assert_equal CHAPTER_NAMES[1], chapter_v2.name
596
+ end
597
+ end
598
+
599
+ context "version 2, before the section was destroyed" do
600
+ setup do
601
+ @chapter.update_attributes :name => CHAPTER_NAMES[2]
602
+ Timecop.travel 1.second.since
603
+ @chapter.sections.destroy_all
604
+ Timecop.travel 1.second.since
605
+ end
606
+
607
+ should "have the one section" do
608
+ chapter_v2 = @chapter.versions[2].reify(:has_many => true)
609
+ assert_equal ['section 2'], chapter_v2.sections.map(&:name)
610
+ end
611
+ end
612
+
613
+ context "version 3, after the section was destroyed" do
614
+ setup do
615
+ @chapter.sections.destroy_all
616
+ Timecop.travel 1.second.since
617
+ @chapter.update_attributes :name => CHAPTER_NAMES[3]
618
+ Timecop.travel 1.second.since
619
+ end
620
+
621
+ should "have no sections" do
622
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
623
+ assert_equal 0, chapter_v3.sections.size
624
+ end
625
+ end
626
+
627
+ context "after creating a paragraph" do
628
+ setup do
629
+ assert_equal 3, @chapter.versions.size
630
+ @section = @chapter.sections.first
631
+ Timecop.travel 1.second.since
632
+ @paragraph = @section.paragraphs.create :name => 'para1'
633
+ end
634
+
635
+ context "new chapter version" do
636
+ should "have one paragraph" do
637
+ initial_section_name = @section.name
638
+ initial_paragraph_name = @paragraph.name
639
+ Timecop.travel 1.second.since
640
+ @chapter.update_attributes :name => CHAPTER_NAMES[4]
641
+ assert_equal 4, @chapter.versions.size
642
+ Timecop.travel 1.second.since
643
+ @paragraph.update_attributes :name => 'para3'
644
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
645
+ assert_equal [initial_section_name], chapter_v3.sections.map(&:name)
646
+ paragraphs = chapter_v3.sections.first.paragraphs
647
+ assert_equal 1, paragraphs.size
648
+ assert_equal [initial_paragraph_name], paragraphs.map(&:name)
649
+ end
650
+ end
651
+
652
+ context "the version before a section is destroyed" do
653
+ should "have the section and paragraph" do
654
+ Timecop.travel 1.second.since
655
+ @chapter.update_attributes(:name => CHAPTER_NAMES[3])
656
+ assert_equal 4, @chapter.versions.size
657
+ Timecop.travel 1.second.since
658
+ @section.destroy
659
+ assert_equal 4, @chapter.versions.size
660
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
661
+ assert_equal CHAPTER_NAMES[2], chapter_v3.name
662
+ assert_equal [@section], chapter_v3.sections
663
+ assert_equal [@paragraph], chapter_v3.sections[0].paragraphs
664
+ assert_equal [@paragraph], chapter_v3.paragraphs
665
+ end
666
+ end
667
+
668
+ context "the version after a section is destroyed" do
669
+ should "not have any sections or paragraphs" do
670
+ @section.destroy
671
+ Timecop.travel 1.second.since
672
+ @chapter.update_attributes(:name => CHAPTER_NAMES[5])
673
+ assert_equal 4, @chapter.versions.size
674
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
675
+ assert_equal 0, chapter_v3.sections.size
676
+ assert_equal 0, chapter_v3.paragraphs.size
677
+ end
678
+ end
679
+
680
+ context "the version before a paragraph is destroyed" do
681
+ should "have the one paragraph" do
682
+ initial_paragraph_name = @section.paragraphs.first.name
683
+ Timecop.travel 1.second.since
684
+ @chapter.update_attributes(:name => CHAPTER_NAMES[5])
685
+ Timecop.travel 1.second.since
686
+ @paragraph.destroy
687
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
688
+ paragraphs = chapter_v3.sections.first.paragraphs
689
+ assert_equal 1, paragraphs.size
690
+ assert_equal initial_paragraph_name, paragraphs.first.name
691
+ end
692
+ end
693
+
694
+ context "the version after a paragraph is destroyed" do
695
+ should "have no paragraphs" do
696
+ @paragraph.destroy
697
+ Timecop.travel 1.second.since
698
+ @chapter.update_attributes(:name => CHAPTER_NAMES[5])
699
+ chapter_v3 = @chapter.versions[3].reify(:has_many => true)
700
+ assert_equal 0, chapter_v3.paragraphs.size
701
+ assert_equal [], chapter_v3.sections.first.paragraphs
702
+ end
703
+ end
704
+ end
705
+ end
706
+
707
+ context "a chapter with one paragraph and one citation" do
708
+ should "reify paragraphs and citations" do
709
+ chapter = Chapter.create(:name => CHAPTER_NAMES[0])
710
+ section = Section.create(:name => 'Section One', :chapter => chapter)
711
+ paragraph = Paragraph.create(:name => 'Paragraph One', :section => section)
712
+ quotation = Quotation.create(:chapter => chapter)
713
+ citation = Citation.create(:quotation => quotation)
714
+ Timecop.travel 1.second.since
715
+ chapter.update_attributes(:name => CHAPTER_NAMES[1])
716
+ assert_equal 2, chapter.versions.count
717
+ paragraph.destroy
718
+ citation.destroy
719
+ reified = chapter.versions[1].reify(:has_many => true)
720
+ assert_equal [paragraph], reified.sections.first.paragraphs
721
+ assert_equal [citation], reified.quotations.first.citations
722
+ end
723
+ end
724
+ end
725
+ end
726
+ end