paper_trail 7.0.2 → 7.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +1 -1
  3. data/.rubocop_todo.yml +10 -0
  4. data/.travis.yml +4 -4
  5. data/Appraisals +3 -5
  6. data/CHANGELOG.md +16 -1
  7. data/README.md +70 -119
  8. data/Rakefile +6 -1
  9. data/doc/bug_report_template.rb +4 -2
  10. data/doc/warning_about_not_setting_whodunnit.md +6 -5
  11. data/gemfiles/ar_4.0.gemfile +1 -1
  12. data/gemfiles/ar_4.2.gemfile +1 -1
  13. data/gemfiles/ar_5.0.gemfile +2 -3
  14. data/gemfiles/ar_5.1.gemfile +8 -0
  15. data/gemfiles/ar_master.gemfile +2 -2
  16. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb.erb +1 -1
  17. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb.erb +1 -1
  18. data/lib/generators/paper_trail/templates/create_version_associations.rb.erb +1 -1
  19. data/lib/paper_trail/model_config.rb +4 -4
  20. data/lib/paper_trail/version_concern.rb +4 -4
  21. data/lib/paper_trail/version_number.rb +1 -1
  22. data/paper_trail.gemspec +5 -5
  23. data/spec/controllers/articles_controller_spec.rb +1 -1
  24. data/spec/generators/install_generator_spec.rb +2 -2
  25. data/spec/models/animal_spec.rb +5 -5
  26. data/spec/models/boolit_spec.rb +2 -2
  27. data/spec/models/callback_modifier_spec.rb +2 -2
  28. data/spec/models/car_spec.rb +2 -2
  29. data/spec/models/custom_primary_key_record_spec.rb +2 -2
  30. data/spec/models/document_spec.rb +2 -2
  31. data/spec/models/gadget_spec.rb +2 -2
  32. data/spec/models/joined_version_spec.rb +1 -1
  33. data/spec/models/json_version_spec.rb +4 -4
  34. data/spec/models/kitchen/banana_spec.rb +2 -2
  35. data/spec/models/not_on_update_spec.rb +2 -2
  36. data/spec/models/post_with_status_spec.rb +4 -4
  37. data/spec/models/skipper_spec.rb +1 -1
  38. data/spec/models/thing_spec.rb +2 -2
  39. data/spec/models/vehicle_spec.rb +2 -2
  40. data/spec/models/version_spec.rb +26 -5
  41. data/spec/models/widget_spec.rb +10 -2
  42. data/spec/modules/paper_trail_spec.rb +2 -2
  43. data/spec/modules/version_concern_spec.rb +2 -2
  44. data/spec/modules/version_number_spec.rb +1 -1
  45. data/spec/paper_trail/associations_spec.rb +965 -0
  46. data/spec/paper_trail/cleaner_spec.rb +2 -2
  47. data/spec/paper_trail/config_spec.rb +2 -2
  48. data/spec/paper_trail/model_spec.rb +1421 -0
  49. data/spec/paper_trail/serializer_spec.rb +85 -0
  50. data/spec/paper_trail/serializers/custom_yaml_serializer_spec.rb +1 -1
  51. data/spec/paper_trail/serializers/json_spec.rb +2 -2
  52. data/spec/paper_trail/serializers/yaml_spec.rb +42 -0
  53. data/spec/paper_trail/version_limit_spec.rb +2 -2
  54. data/spec/paper_trail/version_spec.rb +96 -0
  55. data/spec/paper_trail_spec.rb +1 -1
  56. data/spec/requests/articles_spec.rb +2 -2
  57. data/spec/spec_helper.rb +47 -79
  58. data/{test → spec/support}/custom_json_serializer.rb +0 -0
  59. data/test/dummy/app/models/document.rb +1 -1
  60. data/test/dummy/app/models/not_on_update.rb +1 -1
  61. data/test/dummy/app/models/widget.rb +1 -1
  62. data/test/dummy/config/routes.rb +1 -1
  63. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +18 -9
  64. data/test/dummy/db/schema.rb +64 -64
  65. data/test/test_helper.rb +1 -33
  66. data/test/unit/serializers/mixin_json_test.rb +1 -1
  67. metadata +27 -32
  68. data/spec/models/truck_spec.rb +0 -5
  69. data/spec/rails_helper.rb +0 -34
  70. data/test/time_travel_helper.rb +0 -1
  71. data/test/unit/associations_test.rb +0 -1032
  72. data/test/unit/model_test.rb +0 -1416
  73. data/test/unit/serializer_test.rb +0 -107
  74. data/test/unit/serializers/yaml_test.rb +0 -50
  75. data/test/unit/version_test.rb +0 -112
@@ -1,7 +1,7 @@
1
- require "rails_helper"
1
+ require "spec_helper"
2
2
 
3
3
  module PaperTrail
4
- RSpec.describe Cleaner, versioning: true do
4
+ ::RSpec.describe Cleaner, versioning: true do
5
5
  describe "clean_versions!" do
6
6
  let(:animal) { ::Animal.new }
7
7
  let(:dog) { ::Dog.new }
@@ -1,7 +1,7 @@
1
- require "rails_helper"
1
+ require "spec_helper"
2
2
 
3
3
  module PaperTrail
4
- RSpec.describe Config do
4
+ ::RSpec.describe Config do
5
5
  describe ".instance" do
6
6
  it "returns the singleton instance" do
7
7
  expect { described_class.instance }.not_to raise_error
@@ -0,0 +1,1421 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe(::PaperTrail, versioning: true) do
4
+ context "A record with defined 'only' and 'ignore' attributes" do
5
+ before { @article = Article.create }
6
+
7
+ it "creation should change the number of versions" do
8
+ expect(PaperTrail::Version.count).to(eq(1))
9
+ end
10
+
11
+ context "which updates an ignored column" do
12
+ it "not change the number of versions" do
13
+ @article.update_attributes(title: "My first title")
14
+ expect(PaperTrail::Version.count).to(eq(1))
15
+ end
16
+ end
17
+
18
+ context "which updates an ignored column with truly Proc" do
19
+ it "not change the number of versions" do
20
+ @article.update_attributes(abstract: "ignore abstract")
21
+ expect(PaperTrail::Version.count).to(eq(1))
22
+ end
23
+ end
24
+
25
+ context "which updates an ignored column with falsy Proc" do
26
+ it "change the number of versions" do
27
+ @article.update_attributes(abstract: "do not ignore abstract!")
28
+ expect(PaperTrail::Version.count).to(eq(2))
29
+ end
30
+ end
31
+
32
+ context "which updates an ignored column, ignored with truly Proc and a selected column" do
33
+ before do
34
+ @article.update_attributes(
35
+ title: "My first title",
36
+ content: "Some text here.",
37
+ abstract: "ignore abstract"
38
+ )
39
+ end
40
+
41
+ it "change the number of versions" do
42
+ expect(PaperTrail::Version.count).to(eq(2))
43
+ end
44
+
45
+ it "show the new version in the model's `versions` association" do
46
+ expect(@article.versions.size).to(eq(2))
47
+ end
48
+
49
+ it "have stored only non-ignored attributes" do
50
+ expected = { "content" => [nil, "Some text here."] }
51
+ expect(@article.versions.last.changeset).to(eq(expected))
52
+ end
53
+ end
54
+
55
+ context "which updates an ignored column, ignored with falsy Proc and a selected column" do
56
+ before do
57
+ @article.update_attributes(
58
+ title: "My first title",
59
+ content: "Some text here.",
60
+ abstract: "do not ignore abstract"
61
+ )
62
+ end
63
+
64
+ it "change the number of versions" do
65
+ expect(PaperTrail::Version.count).to(eq(2))
66
+ end
67
+
68
+ it "show the new version in the model's `versions` association" do
69
+ expect(@article.versions.size).to(eq(2))
70
+ end
71
+
72
+ it "have stored only non-ignored attributes" do
73
+ expected = {
74
+ "content" => [nil, "Some text here."],
75
+ "abstract" => [nil, "do not ignore abstract"]
76
+ }
77
+ expect(@article.versions.last.changeset).to(eq(expected))
78
+ end
79
+ end
80
+
81
+ context "which updates a selected column" do
82
+ before { @article.update_attributes(content: "Some text here.") }
83
+
84
+ it "change the number of versions" do
85
+ expect(PaperTrail::Version.count).to(eq(2))
86
+ end
87
+
88
+ it "show the new version in the model's `versions` association" do
89
+ expect(@article.versions.size).to(eq(2))
90
+ end
91
+ end
92
+
93
+ context "which updates a non-ignored and non-selected column" do
94
+ it "not change the number of versions" do
95
+ @article.update_attributes(abstract: "Other abstract")
96
+ expect(PaperTrail::Version.count).to(eq(1))
97
+ end
98
+ end
99
+
100
+ context "which updates a skipped column" do
101
+ it "not change the number of versions" do
102
+ @article.update_attributes(file_upload: "Your data goes here")
103
+ expect(PaperTrail::Version.count).to(eq(1))
104
+ end
105
+ end
106
+
107
+ context "which updates a skipped column and a selected column" do
108
+ before do
109
+ @article.update_attributes(
110
+ file_upload: "Your data goes here",
111
+ content: "Some text here."
112
+ )
113
+ end
114
+
115
+ it "change the number of versions" do
116
+ expect(PaperTrail::Version.count).to(eq(2))
117
+ end
118
+
119
+ it "show the new version in the model's `versions` association" do
120
+ expect(@article.versions.size).to(eq(2))
121
+ end
122
+
123
+ it "have stored only non-skipped attributes" do
124
+ expect(
125
+ @article.versions.last.changeset
126
+ ).to(eq("content" => [nil, "Some text here."]))
127
+ end
128
+
129
+ context "and when updated again" do
130
+ before do
131
+ @article.update_attributes(
132
+ file_upload: "More data goes here",
133
+ content: "More text here."
134
+ )
135
+ @old_article = @article.versions.last
136
+ end
137
+
138
+ it "have removed the skipped attributes when saving the previous version" do
139
+ expect(
140
+ PaperTrail.serializer.load(@old_article.object)["file_upload"]
141
+ ).to(be_nil)
142
+ end
143
+
144
+ it "have kept the non-skipped attributes in the previous version" do
145
+ expect(
146
+ PaperTrail.serializer.load(@old_article.object)["content"]
147
+ ).to(eq("Some text here."))
148
+ end
149
+ end
150
+ end
151
+
152
+ context "which gets destroyed" do
153
+ before { @article.destroy }
154
+
155
+ it "change the number of versions" do
156
+ expect(PaperTrail::Version.count).to(eq(2))
157
+ end
158
+
159
+ it "show the new version in the model's `versions` association" do
160
+ expect(@article.versions.size).to(eq(2))
161
+ end
162
+ end
163
+ end
164
+
165
+ context "A record with defined 'ignore' attribute" do
166
+ before { @legacy_widget = LegacyWidget.create }
167
+
168
+ context "which updates an ignored column" do
169
+ before { @legacy_widget.update_attributes(version: 1) }
170
+
171
+ it "not change the number of versions" do
172
+ expect(PaperTrail::Version.count).to(eq(1))
173
+ end
174
+ end
175
+ end
176
+
177
+ context "A record with defined \"if\" and \"unless\" attributes" do
178
+ before { @translation = Translation.new(headline: "Headline") }
179
+
180
+ context "for non-US translations" do
181
+ before { @translation.save }
182
+
183
+ it "not change the number of versions" do
184
+ expect(PaperTrail::Version.count).to(eq(0))
185
+ end
186
+
187
+ context "after update" do
188
+ before { @translation.update_attributes(content: "Content") }
189
+
190
+ it "not change the number of versions" do
191
+ expect(PaperTrail::Version.count).to(eq(0))
192
+ end
193
+ end
194
+
195
+ context "after destroy" do
196
+ before { @translation.destroy }
197
+
198
+ it "not change the number of versions" do
199
+ expect(PaperTrail::Version.count).to(eq(0))
200
+ end
201
+ end
202
+ end
203
+
204
+ context "for US translations" do
205
+ before { @translation.language_code = "US" }
206
+
207
+ context "that are drafts" do
208
+ before do
209
+ @translation.type = "DRAFT"
210
+ @translation.save
211
+ end
212
+
213
+ it "not change the number of versions" do
214
+ expect(PaperTrail::Version.count).to(eq(0))
215
+ end
216
+
217
+ context "after update" do
218
+ before { @translation.update_attributes(content: "Content") }
219
+
220
+ it "not change the number of versions" do
221
+ expect(PaperTrail::Version.count).to(eq(0))
222
+ end
223
+ end
224
+ end
225
+
226
+ context "that are not drafts" do
227
+ before { @translation.save }
228
+
229
+ it "change the number of versions" do
230
+ expect(PaperTrail::Version.count).to(eq(1))
231
+ end
232
+
233
+ context "after update" do
234
+ before { @translation.update_attributes(content: "Content") }
235
+
236
+ it "change the number of versions" do
237
+ expect(PaperTrail::Version.count).to(eq(2))
238
+ end
239
+
240
+ it "show the new version in the model's `versions` association" do
241
+ expect(@translation.versions.size).to(eq(2))
242
+ end
243
+ end
244
+
245
+ context "after destroy" do
246
+ before { @translation.destroy }
247
+
248
+ it "change the number of versions" do
249
+ expect(PaperTrail::Version.count).to(eq(2))
250
+ end
251
+
252
+ it "show the new version in the model's `versions` association" do
253
+ expect(@translation.versions.size).to(eq(2))
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ context "A new record" do
261
+ before { @widget = Widget.new }
262
+
263
+ it "not have any previous versions" do
264
+ expect(@widget.versions).to(eq([]))
265
+ end
266
+
267
+ it "be live" do
268
+ expect(@widget.paper_trail.live?).to(eq(true))
269
+ end
270
+
271
+ context "which is then created" do
272
+ before do
273
+ @widget.update_attributes(name: "Henry", created_at: (Time.now - 1.day))
274
+ end
275
+
276
+ it "have one previous version" do
277
+ expect(@widget.versions.length).to(eq(1))
278
+ end
279
+
280
+ it "be nil in its previous version" do
281
+ expect(@widget.versions.first.object).to(be_nil)
282
+ expect(@widget.versions.first.reify).to(be_nil)
283
+ end
284
+
285
+ it "record the correct event" do
286
+ expect(@widget.versions.first.event).to(match(/create/i))
287
+ end
288
+
289
+ it "be live" do
290
+ expect(@widget.paper_trail.live?).to(eq(true))
291
+ end
292
+
293
+ it "use the widget `updated_at` as the version's `created_at`" do
294
+ expect(@widget.versions.first.created_at.to_i).to(eq(@widget.updated_at.to_i))
295
+ end
296
+
297
+ describe "#changeset" do
298
+ it "has expected values" do
299
+ changeset = @widget.versions.last.changeset
300
+ expect(changeset["name"]).to eq([nil, "Henry"])
301
+ expect(changeset["id"]).to eq([nil, @widget.id])
302
+ # When comparing timestamps, round off to the nearest second, because
303
+ # mysql doesn't do fractional seconds.
304
+ expect(changeset["created_at"][0]).to be_nil
305
+ expect(changeset["created_at"][1].to_i).to eq(@widget.created_at.to_i)
306
+ expect(changeset["updated_at"][0]).to be_nil
307
+ expect(changeset["updated_at"][1].to_i).to eq(@widget.updated_at.to_i)
308
+ end
309
+ end
310
+
311
+ context "and then updated without any changes" do
312
+ before { @widget.touch }
313
+
314
+ it "not have a new version" do
315
+ expect(@widget.versions.length).to(eq(1))
316
+ end
317
+ end
318
+
319
+ context "and then updated with changes" do
320
+ before { @widget.update_attributes(name: "Harry") }
321
+
322
+ it "have two previous versions" do
323
+ expect(@widget.versions.length).to(eq(2))
324
+ end
325
+
326
+ it "be available in its previous version" do
327
+ expect(@widget.name).to(eq("Harry"))
328
+ expect(@widget.versions.last.object).not_to(be_nil)
329
+ widget = @widget.versions.last.reify
330
+ expect(widget.name).to(eq("Henry"))
331
+ expect(@widget.name).to(eq("Harry"))
332
+ end
333
+
334
+ it "have the same ID in its previous version" do
335
+ expect(@widget.versions.last.reify.id).to(eq(@widget.id))
336
+ end
337
+
338
+ it "record the correct event" do
339
+ expect(@widget.versions.last.event).to(match(/update/i))
340
+ end
341
+
342
+ it "have versions that are not live" do
343
+ expect(
344
+ @widget.versions.map(&:reify).compact.all? { |w| !w.paper_trail.live? }
345
+ ).to(be_truthy)
346
+ end
347
+
348
+ it "have stored changes" do
349
+ last_obj_changes = @widget.versions.last.object_changes
350
+ actual = PaperTrail.serializer.load(last_obj_changes).reject do |k, _v|
351
+ (k.to_sym == :updated_at)
352
+ end
353
+ expect(actual).to(eq("name" => %w[Henry Harry]))
354
+ actual = @widget.versions.last.changeset.reject { |k, _v| (k.to_sym == :updated_at) }
355
+ expect(actual).to(eq("name" => %w[Henry Harry]))
356
+ end
357
+
358
+ it "return changes with indifferent access" do
359
+ expect(@widget.versions.last.changeset[:name]).to(eq(%w[Henry Harry]))
360
+ expect(@widget.versions.last.changeset["name"]).to(eq(%w[Henry Harry]))
361
+ end
362
+
363
+ context "and has one associated object" do
364
+ before { @wotsit = @widget.create_wotsit name: "John" }
365
+
366
+ it "not copy the has_one association by default when reifying" do
367
+ reified_widget = @widget.versions.last.reify
368
+ expect(reified_widget.wotsit).to(eq(@wotsit))
369
+ expect(@widget.reload.wotsit).to(eq(@wotsit))
370
+ end
371
+
372
+ it "copy the has_one association when reifying with :has_one => true" do
373
+ reified_widget = @widget.versions.last.reify(has_one: true)
374
+ expect(reified_widget.wotsit).to(be_nil)
375
+ expect(@widget.reload.wotsit).to(eq(@wotsit))
376
+ end
377
+ end
378
+
379
+ context "and has many associated objects" do
380
+ before do
381
+ @f0 = @widget.fluxors.create(name: "f-zero")
382
+ @f1 = @widget.fluxors.create(name: "f-one")
383
+ @reified_widget = @widget.versions.last.reify
384
+ end
385
+
386
+ it "copy the has_many associations when reifying" do
387
+ expect(@reified_widget.fluxors.length).to(eq(@widget.fluxors.length))
388
+ expect(@reified_widget.fluxors).to match_array(@widget.fluxors)
389
+ expect(@reified_widget.versions.length).to(eq(@widget.versions.length))
390
+ expect(@reified_widget.versions).to match_array(@widget.versions)
391
+ end
392
+ end
393
+
394
+ context "and has many associated polymorphic objects" do
395
+ before do
396
+ @f0 = @widget.whatchamajiggers.create(name: "f-zero")
397
+ @f1 = @widget.whatchamajiggers.create(name: "f-zero")
398
+ @reified_widget = @widget.versions.last.reify
399
+ end
400
+
401
+ it "copy the has_many associations when reifying" do
402
+ expect(@reified_widget.whatchamajiggers.length).to eq(@widget.whatchamajiggers.length)
403
+ expect(@reified_widget.whatchamajiggers).to match_array(@widget.whatchamajiggers)
404
+ expect(@reified_widget.versions.length).to(eq(@widget.versions.length))
405
+ expect(@reified_widget.versions).to match_array(@widget.versions)
406
+ end
407
+ end
408
+
409
+ context "polymorphic objects by themselves" do
410
+ before { @widget = Whatchamajigger.new(name: "f-zero") }
411
+
412
+ it "not fail with a nil pointer on the polymorphic association" do
413
+ @widget.save!
414
+ end
415
+ end
416
+
417
+ context "and then destroyed" do
418
+ before do
419
+ @fluxor = @widget.fluxors.create(name: "flux")
420
+ @widget.destroy
421
+ @reified_widget = PaperTrail::Version.last.reify
422
+ end
423
+
424
+ it "record the correct event" do
425
+ expect(PaperTrail::Version.last.event).to(match(/destroy/i))
426
+ end
427
+
428
+ it "have three previous versions" do
429
+ expect(PaperTrail::Version.with_item_keys("Widget", @widget.id).length).to(eq(3))
430
+ end
431
+
432
+ describe "#attributes" do
433
+ it "returns the expected attributes for the reified widget" do
434
+ expect(@reified_widget.id).to(eq(@widget.id))
435
+ expected = @widget.attributes
436
+ actual = @reified_widget.attributes
437
+ expect(expected["id"]).to eq(actual["id"])
438
+ expect(expected["name"]).to eq(actual["name"])
439
+ expect(expected["a_text"]).to eq(actual["a_text"])
440
+ expect(expected["an_integer"]).to eq(actual["an_integer"])
441
+ expect(expected["a_float"]).to eq(actual["a_float"])
442
+ expect(expected["a_decimal"]).to eq(actual["a_decimal"])
443
+ expect(expected["a_datetime"]).to eq(actual["a_datetime"])
444
+ expect(expected["a_time"]).to eq(actual["a_time"])
445
+ expect(expected["a_date"]).to eq(actual["a_date"])
446
+ expect(expected["a_boolean"]).to eq(actual["a_boolean"])
447
+ expect(expected["type"]).to eq(actual["type"])
448
+ expect(expected["created_at"].to_i).to eq(actual["created_at"].to_i)
449
+ expect(expected["updated_at"].to_i).to eq(actual["updated_at"].to_i)
450
+ end
451
+ end
452
+
453
+ it "be re-creatable from its previous version" do
454
+ expect(@reified_widget.save).to(be_truthy)
455
+ end
456
+
457
+ it "restore its associations on its previous version" do
458
+ @reified_widget.save
459
+ expect(@reified_widget.fluxors.length).to(eq(1))
460
+ end
461
+
462
+ it "have nil item for last version" do
463
+ expect(@widget.versions.last.item).to(be_nil)
464
+ end
465
+
466
+ it "not have changes" do
467
+ expect(@widget.versions.last.changeset).to(eq({}))
468
+ end
469
+ end
470
+ end
471
+ end
472
+ end
473
+
474
+ context "A record's papertrail" do
475
+ before do
476
+ @date_time = DateTime.now.utc
477
+ @time = Time.now
478
+ @date = Date.new(2009, 5, 29)
479
+ @widget = Widget.create(
480
+ name: "Warble",
481
+ a_text: "The quick brown fox",
482
+ an_integer: 42,
483
+ a_float: 153.01,
484
+ a_decimal: 2.71828,
485
+ a_datetime: @date_time,
486
+ a_time: @time,
487
+ a_date: @date,
488
+ a_boolean: true
489
+ )
490
+ @widget.update_attributes(
491
+ name: nil,
492
+ a_text: nil,
493
+ an_integer: nil,
494
+ a_float: nil,
495
+ a_decimal: nil,
496
+ a_datetime: nil,
497
+ a_time: nil,
498
+ a_date: nil,
499
+ a_boolean: false
500
+ )
501
+ @previous = @widget.versions.last.reify
502
+ end
503
+
504
+ it "handle strings" do
505
+ expect(@previous.name).to(eq("Warble"))
506
+ end
507
+
508
+ it "handle text" do
509
+ expect(@previous.a_text).to(eq("The quick brown fox"))
510
+ end
511
+
512
+ it "handle integers" do
513
+ expect(@previous.an_integer).to(eq(42))
514
+ end
515
+
516
+ it "handle floats" do
517
+ assert_in_delta(153.01, @previous.a_float, 0.001)
518
+ end
519
+
520
+ it "handle decimals" do
521
+ assert_in_delta(2.7183, @previous.a_decimal, 0.0001)
522
+ end
523
+
524
+ it "handle datetimes" do
525
+ expect(@previous.a_datetime.to_time.utc.to_i).to(eq(@date_time.to_time.utc.to_i))
526
+ end
527
+
528
+ it "handle times" do
529
+ expect(@previous.a_time.utc.to_i).to(eq(@time.utc.to_i))
530
+ end
531
+
532
+ it "handle dates" do
533
+ expect(@previous.a_date).to(eq(@date))
534
+ end
535
+
536
+ it "handle booleans" do
537
+ expect(@previous.a_boolean).to(be_truthy)
538
+ end
539
+
540
+ context "after a column is removed from the record's schema" do
541
+ before { @last = @widget.versions.last }
542
+
543
+ it "reify previous version" do
544
+ assert_kind_of(Widget, @last.reify)
545
+ end
546
+
547
+ it "restore all forward-compatible attributes" do
548
+ expect(@last.reify.name).to(eq("Warble"))
549
+ expect(@last.reify.a_text).to(eq("The quick brown fox"))
550
+ expect(@last.reify.an_integer).to(eq(42))
551
+ assert_in_delta(153.01, @last.reify.a_float, 0.001)
552
+ assert_in_delta(2.7183, @last.reify.a_decimal, 0.0001)
553
+ expect(@last.reify.a_datetime.to_time.utc.to_i).to(eq(@date_time.to_time.utc.to_i))
554
+ expect(@last.reify.a_time.utc.to_i).to(eq(@time.utc.to_i))
555
+ expect(@last.reify.a_date).to(eq(@date))
556
+ expect(@last.reify.a_boolean).to(be_truthy)
557
+ end
558
+ end
559
+ end
560
+
561
+ context "A record" do
562
+ before { @widget = Widget.create(name: "Zaphod") }
563
+
564
+ context "with PaperTrail globally disabled" do
565
+ before do
566
+ PaperTrail.enabled = false
567
+ @count = @widget.versions.length
568
+ end
569
+
570
+ after { PaperTrail.enabled = true }
571
+
572
+ context "when updated" do
573
+ before { @widget.update_attributes(name: "Beeblebrox") }
574
+
575
+ it "not add to its trail" do
576
+ expect(@widget.versions.length).to(eq(@count))
577
+ end
578
+ end
579
+ end
580
+
581
+ context "with its paper trail turned off" do
582
+ before do
583
+ Widget.paper_trail.disable
584
+ @count = @widget.versions.length
585
+ end
586
+
587
+ after { Widget.paper_trail.enable }
588
+
589
+ context "when updated" do
590
+ before { @widget.update_attributes(name: "Beeblebrox") }
591
+
592
+ it "not add to its trail" do
593
+ expect(@widget.versions.length).to(eq(@count))
594
+ end
595
+ end
596
+
597
+ context "when destroyed \"without versioning\"" do
598
+ it "leave paper trail off after call" do
599
+ @widget.paper_trail.without_versioning(:destroy)
600
+ expect(Widget.paper_trail.enabled?).to(eq(false))
601
+ end
602
+ end
603
+
604
+ context "and then its paper trail turned on" do
605
+ before { Widget.paper_trail.enable }
606
+
607
+ context "when updated" do
608
+ before { @widget.update_attributes(name: "Ford") }
609
+
610
+ it "add to its trail" do
611
+ expect(@widget.versions.length).to(eq((@count + 1)))
612
+ end
613
+ end
614
+
615
+ context "when updated \"without versioning\"" do
616
+ before do
617
+ @widget.paper_trail.without_versioning do
618
+ @widget.update_attributes(name: "Ford")
619
+ end
620
+ @widget.paper_trail.without_versioning do |w|
621
+ w.update_attributes(name: "Nixon")
622
+ end
623
+ end
624
+
625
+ it "not create new version" do
626
+ expect(@widget.versions.length).to(eq(@count))
627
+ end
628
+
629
+ it "enable paper trail after call" do
630
+ expect(Widget.paper_trail.enabled?).to(eq(true))
631
+ end
632
+ end
633
+
634
+ context "when receiving a method name as an argument" do
635
+ before { @widget.paper_trail.without_versioning(:touch_with_version) }
636
+
637
+ it "not create new version" do
638
+ expect(@widget.versions.length).to(eq(@count))
639
+ end
640
+
641
+ it "enable paper trail after call" do
642
+ expect(Widget.paper_trail.enabled?).to(eq(true))
643
+ end
644
+ end
645
+ end
646
+ end
647
+ end
648
+
649
+ context "A papertrail with somebody making changes" do
650
+ before { @widget = Widget.new(name: "Fidget") }
651
+
652
+ context "when a record is created" do
653
+ before do
654
+ PaperTrail.whodunnit = "Alice"
655
+ @widget.save
656
+ @version = @widget.versions.last
657
+ end
658
+
659
+ it "track who made the change" do
660
+ expect(@version.whodunnit).to(eq("Alice"))
661
+ expect(@version.paper_trail_originator).to(be_nil)
662
+ expect(@version.terminator).to(eq("Alice"))
663
+ expect(@widget.paper_trail.originator).to(eq("Alice"))
664
+ end
665
+
666
+ context "when a record is updated" do
667
+ before do
668
+ PaperTrail.whodunnit = "Bob"
669
+ @widget.update_attributes(name: "Rivet")
670
+ @version = @widget.versions.last
671
+ end
672
+
673
+ it "track who made the change" do
674
+ expect(@version.whodunnit).to(eq("Bob"))
675
+ expect(@version.paper_trail_originator).to(eq("Alice"))
676
+ expect(@version.terminator).to(eq("Bob"))
677
+ expect(@widget.paper_trail.originator).to(eq("Bob"))
678
+ end
679
+
680
+ context "when a record is destroyed" do
681
+ before do
682
+ PaperTrail.whodunnit = "Charlie"
683
+ @widget.destroy
684
+ @version = PaperTrail::Version.last
685
+ end
686
+
687
+ it "track who made the change" do
688
+ expect(@version.whodunnit).to(eq("Charlie"))
689
+ expect(@version.paper_trail_originator).to(eq("Bob"))
690
+ expect(@version.terminator).to(eq("Charlie"))
691
+ expect(@widget.paper_trail.originator).to(eq("Charlie"))
692
+ end
693
+ end
694
+ end
695
+ end
696
+ end
697
+
698
+ it "update_attributes! records timestamps" do
699
+ wotsit = Wotsit.create!(name: "wotsit")
700
+ wotsit.update_attributes!(name: "changed")
701
+ reified = wotsit.versions.last.reify
702
+ expect(reified.created_at).not_to(be_nil)
703
+ expect(reified.updated_at).not_to(be_nil)
704
+ end
705
+
706
+ it "update_attributes! does not raise error" do
707
+ wotsit = Wotsit.create!(name: "name1")
708
+ expect { wotsit.update_attributes!(name: "name2") }.not_to(raise_error)
709
+ end
710
+
711
+ context "A subclass" do
712
+ before do
713
+ @foo = FooWidget.create
714
+ @foo.update_attributes!(name: "Foo")
715
+ end
716
+
717
+ it "reify with the correct type" do
718
+ if ActiveRecord::VERSION::MAJOR < 4
719
+ assert_kind_of(FooWidget, @foo.versions.last.reify)
720
+ end
721
+ expect(PaperTrail::Version.last.previous).to(eq(@foo.versions.first))
722
+ expect(PaperTrail::Version.last.next).to(be_nil)
723
+ end
724
+
725
+ it "returns the correct originator" do
726
+ PaperTrail.whodunnit = "Ben"
727
+ @foo.update_attribute(:name, "Geoffrey")
728
+ expect(@foo.paper_trail.originator).to(eq(PaperTrail.whodunnit))
729
+ end
730
+
731
+ context "when destroyed" do
732
+ before { @foo.destroy }
733
+
734
+ it "reify with the correct type" do
735
+ assert_kind_of(FooWidget, @foo.versions.last.reify)
736
+ expect(PaperTrail::Version.last.previous).to(eq(@foo.versions[1]))
737
+ expect(PaperTrail::Version.last.next).to(be_nil)
738
+ end
739
+ end
740
+ end
741
+
742
+ context "An item with versions" do
743
+ before do
744
+ @widget = Widget.create(name: "Widget")
745
+ @widget.update_attributes(name: "Fidget")
746
+ @widget.update_attributes(name: "Digit")
747
+ end
748
+
749
+ context "which were created over time" do
750
+ before do
751
+ @created = 2.days.ago
752
+ @first_update = 1.day.ago
753
+ @second_update = 1.hour.ago
754
+ @widget.versions[0].update_attributes(created_at: @created)
755
+ @widget.versions[1].update_attributes(created_at: @first_update)
756
+ @widget.versions[2].update_attributes(created_at: @second_update)
757
+ @widget.update_attribute(:updated_at, @second_update)
758
+ end
759
+
760
+ it "return nil for version_at before it was created" do
761
+ expect(@widget.paper_trail.version_at((@created - 1))).to(be_nil)
762
+ end
763
+
764
+ it "return how it looked when created for version_at its creation" do
765
+ expect(@widget.paper_trail.version_at(@created).name).to(eq("Widget"))
766
+ end
767
+
768
+ it "return how it looked before its first update" do
769
+ expect(@widget.paper_trail.version_at((@first_update - 1)).name).to(eq("Widget"))
770
+ end
771
+
772
+ it "return how it looked after its first update" do
773
+ expect(@widget.paper_trail.version_at(@first_update).name).to(eq("Fidget"))
774
+ end
775
+
776
+ it "return how it looked before its second update" do
777
+ expect(@widget.paper_trail.version_at((@second_update - 1)).name).to(eq("Fidget"))
778
+ end
779
+
780
+ it "return how it looked after its second update" do
781
+ expect(@widget.paper_trail.version_at(@second_update).name).to(eq("Digit"))
782
+ end
783
+
784
+ it "return the current object for version_at after latest update" do
785
+ expect(@widget.paper_trail.version_at(1.day.from_now).name).to(eq("Digit"))
786
+ end
787
+
788
+ context "passing in a string representation of a timestamp" do
789
+ it "still return a widget when appropriate" do
790
+ expect(
791
+ @widget.paper_trail.version_at((@created + 1.second).to_s).name
792
+ ).to(eq("Widget"))
793
+ expect(
794
+ @widget.paper_trail.version_at((@first_update + 1.second).to_s).name
795
+ ).to(eq("Fidget"))
796
+ expect(
797
+ @widget.paper_trail.version_at((@second_update + 1.second).to_s).name
798
+ ).to(eq("Digit"))
799
+ end
800
+ end
801
+ end
802
+
803
+ context ".versions_between" do
804
+ before do
805
+ @created = 30.days.ago
806
+ @first_update = 15.days.ago
807
+ @second_update = 1.day.ago
808
+ @widget.versions[0].update_attributes(created_at: @created)
809
+ @widget.versions[1].update_attributes(created_at: @first_update)
810
+ @widget.versions[2].update_attributes(created_at: @second_update)
811
+ @widget.update_attribute(:updated_at, @second_update)
812
+ end
813
+
814
+ it "return versions in the time period" do
815
+ expect(
816
+ @widget.paper_trail.versions_between(20.days.ago, 10.days.ago).map(&:name)
817
+ ).to(eq(["Fidget"]))
818
+ expect(
819
+ @widget.paper_trail.versions_between(45.days.ago, 10.days.ago).map(&:name)
820
+ ).to(eq(%w[Widget Fidget]))
821
+ expect(
822
+ @widget.paper_trail.versions_between(16.days.ago, 1.minute.ago).map(&:name)
823
+ ).to(eq(%w[Fidget Digit Digit]))
824
+ expect(
825
+ @widget.paper_trail.versions_between(60.days.ago, 45.days.ago).map(&:name)
826
+ ).to(eq([]))
827
+ end
828
+ end
829
+
830
+ context "on the first version" do
831
+ before { @version = @widget.versions.first }
832
+
833
+ it "have a nil previous version" do
834
+ expect(@version.previous).to(be_nil)
835
+ end
836
+
837
+ it "return the next version" do
838
+ expect(@version.next).to(eq(@widget.versions[1]))
839
+ end
840
+
841
+ it "return the correct index" do
842
+ expect(@version.index).to(eq(0))
843
+ end
844
+ end
845
+
846
+ context "on the last version" do
847
+ before { @version = @widget.versions.last }
848
+
849
+ it "return the previous version" do
850
+ expect(@version.previous).to(eq(@widget.versions[(@widget.versions.length - 2)]))
851
+ end
852
+
853
+ it "have a nil next version" do
854
+ expect(@version.next).to(be_nil)
855
+ end
856
+
857
+ it "return the correct index" do
858
+ expect(@version.index).to(eq((@widget.versions.length - 1)))
859
+ end
860
+ end
861
+ end
862
+
863
+ context "An item" do
864
+ before do
865
+ @initial_title = "Foobar"
866
+ @article = Article.new(title: @initial_title)
867
+ end
868
+
869
+ context "which is created" do
870
+ before { @article.save }
871
+
872
+ it "store fixed meta data" do
873
+ expect(@article.versions.last.answer).to(eq(42))
874
+ end
875
+
876
+ it "store dynamic meta data which is independent of the item" do
877
+ expect(@article.versions.last.question).to(eq("31 + 11 = 42"))
878
+ end
879
+
880
+ it "store dynamic meta data which depends on the item" do
881
+ expect(@article.versions.last.article_id).to(eq(@article.id))
882
+ end
883
+
884
+ it "store dynamic meta data based on a method of the item" do
885
+ expect(@article.versions.last.action).to(eq(@article.action_data_provider_method))
886
+ end
887
+
888
+ it "store dynamic meta data based on an attribute of the item at creation" do
889
+ expect(@article.versions.last.title).to(eq(@initial_title))
890
+ end
891
+
892
+ context "and updated" do
893
+ before do
894
+ @article.update_attributes!(content: "Better text.", title: "Rhubarb")
895
+ end
896
+
897
+ it "store fixed meta data" do
898
+ expect(@article.versions.last.answer).to(eq(42))
899
+ end
900
+
901
+ it "store dynamic meta data which is independent of the item" do
902
+ expect(@article.versions.last.question).to(eq("31 + 11 = 42"))
903
+ end
904
+
905
+ it "store dynamic meta data which depends on the item" do
906
+ expect(@article.versions.last.article_id).to(eq(@article.id))
907
+ end
908
+
909
+ it "store dynamic meta data based on an attribute of the item prior to the update" do
910
+ expect(@article.versions.last.title).to(eq(@initial_title))
911
+ end
912
+ end
913
+
914
+ context "and destroyed" do
915
+ before { @article.destroy }
916
+
917
+ it "store fixed metadata" do
918
+ expect(@article.versions.last.answer).to(eq(42))
919
+ end
920
+
921
+ it "store dynamic metadata which is independent of the item" do
922
+ expect(@article.versions.last.question).to(eq("31 + 11 = 42"))
923
+ end
924
+
925
+ it "store dynamic metadata which depends on the item" do
926
+ expect(@article.versions.last.article_id).to(eq(@article.id))
927
+ end
928
+
929
+ it "store dynamic metadata based on attribute of item prior to destruction" do
930
+ expect(@article.versions.last.title).to(eq(@initial_title))
931
+ end
932
+ end
933
+ end
934
+ end
935
+
936
+ context "A reified item" do
937
+ before do
938
+ widget = Widget.create(name: "Bob")
939
+ %w[Tom Dick Jane].each do |name|
940
+ widget.update_attributes(name: name)
941
+ end
942
+ @version = widget.versions.last
943
+ @widget = @version.reify
944
+ end
945
+
946
+ it "know which version it came from" do
947
+ expect(@widget.version).to(eq(@version))
948
+ end
949
+
950
+ it "return its previous self" do
951
+ expect(@widget.paper_trail.previous_version).to(eq(@widget.versions[-2].reify))
952
+ end
953
+ end
954
+
955
+ context "A non-reified item" do
956
+ before { @widget = Widget.new }
957
+
958
+ it "not have a previous version" do
959
+ expect(@widget.paper_trail.previous_version).to(be_nil)
960
+ end
961
+
962
+ it "not have a next version" do
963
+ expect(@widget.paper_trail.next_version).to(be_nil)
964
+ end
965
+
966
+ context "with versions" do
967
+ before do
968
+ @widget.save
969
+ %w[Tom Dick Jane].each do |name|
970
+ @widget.update_attributes(name: name)
971
+ end
972
+ end
973
+
974
+ it "have a previous version" do
975
+ expect(@widget.paper_trail.previous_version.name).to(eq(@widget.versions.last.reify.name))
976
+ end
977
+
978
+ it "not have a next version" do
979
+ expect(@widget.paper_trail.next_version).to(be_nil)
980
+ end
981
+ end
982
+ end
983
+
984
+ context "A reified item" do
985
+ before do
986
+ @widget = Widget.create(name: "Bob")
987
+ %w[Tom Dick Jane].each do |name|
988
+ @widget.update_attributes(name: name)
989
+ end
990
+ @second_widget = @widget.versions[1].reify
991
+ @last_widget = @widget.versions.last.reify
992
+ end
993
+
994
+ it "have a previous version" do
995
+ expect(@second_widget.paper_trail.previous_version).to(be_nil)
996
+ expect(@last_widget.paper_trail.previous_version.name).to(eq(@widget.versions[-2].reify.name))
997
+ end
998
+
999
+ it "have a next version" do
1000
+ expect(@second_widget.paper_trail.next_version.name).to(eq(@widget.versions[2].reify.name))
1001
+ expect(@widget.name).to(eq(@last_widget.paper_trail.next_version.name))
1002
+ end
1003
+ end
1004
+
1005
+ context ":has_many :through" do
1006
+ before do
1007
+ @book = Book.create(title: "War and Peace")
1008
+ @dostoyevsky = Person.create(name: "Dostoyevsky")
1009
+ @solzhenitsyn = Person.create(name: "Solzhenitsyn")
1010
+ end
1011
+
1012
+ it "store version on source <<" do
1013
+ count = PaperTrail::Version.count
1014
+ (@book.authors << @dostoyevsky)
1015
+ expect((PaperTrail::Version.count - count)).to(eq(1))
1016
+ expect(@book.authorships.first.versions.first).to(eq(PaperTrail::Version.last))
1017
+ end
1018
+
1019
+ it "store version on source create" do
1020
+ count = PaperTrail::Version.count
1021
+ @book.authors.create(name: "Tolstoy")
1022
+ expect((PaperTrail::Version.count - count)).to(eq(2))
1023
+ expect(
1024
+ [PaperTrail::Version.order(:id).to_a[-2].item, PaperTrail::Version.last.item]
1025
+ ).to match_array([Person.last, Authorship.last])
1026
+ end
1027
+
1028
+ it "store version on join destroy" do
1029
+ (@book.authors << @dostoyevsky)
1030
+ count = PaperTrail::Version.count
1031
+ @book.authorships.reload.last.destroy
1032
+ expect((PaperTrail::Version.count - count)).to(eq(1))
1033
+ expect(PaperTrail::Version.last.reify.book).to(eq(@book))
1034
+ expect(PaperTrail::Version.last.reify.author).to(eq(@dostoyevsky))
1035
+ end
1036
+
1037
+ it "store version on join clear" do
1038
+ (@book.authors << @dostoyevsky)
1039
+ count = PaperTrail::Version.count
1040
+ @book.authorships.reload.destroy_all
1041
+ expect((PaperTrail::Version.count - count)).to(eq(1))
1042
+ expect(PaperTrail::Version.last.reify.book).to(eq(@book))
1043
+ expect(PaperTrail::Version.last.reify.author).to(eq(@dostoyevsky))
1044
+ end
1045
+ end
1046
+
1047
+ context "When an attribute has a custom serializer" do
1048
+ before { @person = Person.new(time_zone: "Samoa") }
1049
+
1050
+ it "be an instance of ActiveSupport::TimeZone" do
1051
+ expect(@person.time_zone.class).to(eq(ActiveSupport::TimeZone))
1052
+ end
1053
+
1054
+ context "when the model is saved" do
1055
+ before do
1056
+ @changes_before_save = @person.changes.dup
1057
+ @person.save!
1058
+ end
1059
+
1060
+ it "version.object_changes should store long serialization of TimeZone object" do
1061
+ len = @person.versions.last.object_changes.length
1062
+ expect((len < 105)).to(be_truthy)
1063
+ end
1064
+
1065
+ it "version.object_changes attribute should have stored the value from serializer" do
1066
+ as_stored_in_version = HashWithIndifferentAccess[
1067
+ YAML.load(@person.versions.last.object_changes)
1068
+ ]
1069
+ expect(as_stored_in_version[:time_zone]).to(eq([nil, "Samoa"]))
1070
+ serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
1071
+ expect(as_stored_in_version[:time_zone].last).to(eq(serialized_value))
1072
+ end
1073
+
1074
+ it "version.changeset should convert attribute to original, unserialized value" do
1075
+ unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
1076
+ expect(@person.versions.last.changeset[:time_zone].last).to(eq(unserialized_value))
1077
+ end
1078
+
1079
+ it "record.changes (before save) returns the original, unserialized values" do
1080
+ expect(
1081
+ @changes_before_save[:time_zone].map(&:class)
1082
+ ).to(eq([NilClass, ActiveSupport::TimeZone]))
1083
+ end
1084
+
1085
+ it "version.changeset should be the same as record.changes was before the save" do
1086
+ actual = @person.versions.last.changeset.delete_if { |k, _v| (k.to_sym == :id) }
1087
+ expect(actual).to(eq(@changes_before_save))
1088
+ actual = @person.versions.last.changeset[:time_zone].map(&:class)
1089
+ expect(actual).to(eq([NilClass, ActiveSupport::TimeZone]))
1090
+ end
1091
+
1092
+ context "when that attribute is updated" do
1093
+ before do
1094
+ @attribute_value_before_change = @person.time_zone
1095
+ @person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
1096
+ @changes_before_save = @person.changes.dup
1097
+ @person.save!
1098
+ end
1099
+
1100
+ it "object should not store long serialization of TimeZone object" do
1101
+ len = @person.versions.last.object.length
1102
+ expect((len < 105)).to(be_truthy)
1103
+ end
1104
+
1105
+ it "object_changes should not store long serialization of TimeZone object" do
1106
+ max_len = ActiveRecord::VERSION::MAJOR < 4 ? 105 : 118
1107
+ len = @person.versions.last.object_changes.length
1108
+ expect((len < max_len)).to(be_truthy)
1109
+ end
1110
+
1111
+ it "version.object attribute should have stored value from serializer" do
1112
+ as_stored_in_version = HashWithIndifferentAccess[
1113
+ YAML.load(@person.versions.last.object)
1114
+ ]
1115
+ expect(as_stored_in_version[:time_zone]).to(eq("Samoa"))
1116
+ serialized_value = Person::TimeZoneSerializer.dump(@attribute_value_before_change)
1117
+ expect(as_stored_in_version[:time_zone]).to(eq(serialized_value))
1118
+ end
1119
+
1120
+ it "version.object_changes attribute should have stored value from serializer" do
1121
+ as_stored_in_version = HashWithIndifferentAccess[
1122
+ YAML.load(@person.versions.last.object_changes)
1123
+ ]
1124
+ expect(as_stored_in_version[:time_zone]).to(eq(["Samoa", "Pacific Time (US & Canada)"]))
1125
+ serialized_value = Person::TimeZoneSerializer.dump(@person.time_zone)
1126
+ expect(as_stored_in_version[:time_zone].last).to(eq(serialized_value))
1127
+ end
1128
+
1129
+ it "version.reify should convert attribute to original, unserialized value" do
1130
+ unserialized_value = Person::TimeZoneSerializer.load(@attribute_value_before_change)
1131
+ expect(@person.versions.last.reify.time_zone).to(eq(unserialized_value))
1132
+ end
1133
+
1134
+ it "version.changeset should convert attribute to original, unserialized value" do
1135
+ unserialized_value = Person::TimeZoneSerializer.load(@person.time_zone)
1136
+ expect(@person.versions.last.changeset[:time_zone].last).to(eq(unserialized_value))
1137
+ end
1138
+
1139
+ it "record.changes (before save) returns the original, unserialized values" do
1140
+ expect(
1141
+ @changes_before_save[:time_zone].map(&:class)
1142
+ ).to(eq([ActiveSupport::TimeZone, ActiveSupport::TimeZone]))
1143
+ end
1144
+
1145
+ it "version.changeset should be the same as record.changes was before the save" do
1146
+ expect(@person.versions.last.changeset).to(eq(@changes_before_save))
1147
+ expect(
1148
+ @person.versions.last.changeset[:time_zone].map(&:class)
1149
+ ).to(eq([ActiveSupport::TimeZone, ActiveSupport::TimeZone]))
1150
+ end
1151
+ end
1152
+ end
1153
+ end
1154
+
1155
+ context "A new model instance which uses a custom PaperTrail::Version class" do
1156
+ before { @post = Post.new }
1157
+
1158
+ context "which is then saved" do
1159
+ before { @post.save }
1160
+
1161
+ it "change the number of post versions" do
1162
+ expect(PostVersion.count).to(eq(1))
1163
+ end
1164
+
1165
+ it "not change the number of versions" do
1166
+ expect(PaperTrail::Version.count).to(eq(0))
1167
+ end
1168
+ end
1169
+ end
1170
+
1171
+ context "An existing model instance which uses a custom PaperTrail::Version class" do
1172
+ before { @post = Post.create }
1173
+
1174
+ it "have one post version" do
1175
+ expect(PostVersion.count).to(eq(1))
1176
+ end
1177
+
1178
+ context "on the first version" do
1179
+ before { @version = @post.versions.first }
1180
+
1181
+ it "have the correct index" do
1182
+ expect(@version.index).to(eq(0))
1183
+ end
1184
+ end
1185
+
1186
+ it "have versions of the custom class" do
1187
+ expect(@post.versions.first.class.name).to(eq("PostVersion"))
1188
+ end
1189
+
1190
+ context "which is modified" do
1191
+ before { @post.update_attributes(content: "Some new content") }
1192
+
1193
+ it "change the number of post versions" do
1194
+ expect(PostVersion.count).to(eq(2))
1195
+ end
1196
+
1197
+ it "not change the number of versions" do
1198
+ expect(PaperTrail::Version.count).to(eq(0))
1199
+ end
1200
+
1201
+ it "not have stored changes when object_changes column doesn't exist" do
1202
+ expect(@post.versions.last.changeset).to(be_nil)
1203
+ end
1204
+ end
1205
+ end
1206
+
1207
+ context "An overwritten default accessor" do
1208
+ before do
1209
+ @song = Song.create(length: 4)
1210
+ @song.update_attributes(length: 5)
1211
+ end
1212
+
1213
+ it "return \"overwritten\" value on live instance" do
1214
+ expect(@song.length).to(eq(5))
1215
+ end
1216
+
1217
+ it "return \"overwritten\" value on reified instance" do
1218
+ expect(@song.versions.last.reify.length).to(eq(4))
1219
+ end
1220
+
1221
+ context "Has a virtual attribute injected into the ActiveModel::Dirty changes" do
1222
+ before do
1223
+ @song.name = "Good Vibrations"
1224
+ @song.save
1225
+ @song.name = "Yellow Submarine"
1226
+ end
1227
+
1228
+ it "return persist the changes on the live instance properly" do
1229
+ expect(@song.name).to(eq("Yellow Submarine"))
1230
+ end
1231
+
1232
+ it "return \"overwritten\" virtual attribute on the reified instance" do
1233
+ expect(@song.versions.last.reify.name).to(eq("Good Vibrations"))
1234
+ end
1235
+ end
1236
+ end
1237
+
1238
+ context "An unsaved record" do
1239
+ before do
1240
+ @widget = Widget.new
1241
+ @widget.destroy
1242
+ end
1243
+
1244
+ it "not have a version created on destroy" do
1245
+ expect(@widget.versions.empty?).to(eq(true))
1246
+ end
1247
+ end
1248
+
1249
+ context "A model with a custom association" do
1250
+ before do
1251
+ @doc = Document.create
1252
+ @doc.update_attributes(name: "Doc 1")
1253
+ end
1254
+
1255
+ it "not respond to versions method" do
1256
+ expect(!@doc.respond_to?(:versions)).to(be_truthy)
1257
+ end
1258
+
1259
+ it "create a new version record" do
1260
+ expect(@doc.paper_trail_versions.length).to(eq(2))
1261
+ end
1262
+
1263
+ it "respond to `next_version` as normal" do
1264
+ reified = @doc.paper_trail_versions.last.reify
1265
+ expect(@doc.name).to(eq(reified.paper_trail.next_version.name))
1266
+ end
1267
+
1268
+ it "respond to `previous_version` as normal" do
1269
+ @doc.update_attributes(name: "Doc 2")
1270
+ expect(@doc.paper_trail_versions.length).to(eq(3))
1271
+ expect(@doc.paper_trail.previous_version.name).to(eq("Doc 1"))
1272
+ end
1273
+ end
1274
+
1275
+ context "The `on` option" do
1276
+ context "on create" do
1277
+ it "only have a version for the create event" do
1278
+ record = ::On::Create.create(name: "Alice")
1279
+ record.update_attributes(name: "blah")
1280
+ record.destroy
1281
+ expect(record.versions.length).to(eq(1))
1282
+ expect(record.versions.last.event).to(eq("create"))
1283
+ end
1284
+ end
1285
+
1286
+ context "on update" do
1287
+ it "only have a version for the update event" do
1288
+ record = ::On::Update.create(name: "Alice")
1289
+ record.update_attributes(name: "blah")
1290
+ record.destroy
1291
+ expect(record.versions.length).to(eq(1))
1292
+ expect(record.versions.last.event).to(eq("update"))
1293
+ end
1294
+ end
1295
+
1296
+ context "on destroy" do
1297
+ it "only have a version for the destroy event" do
1298
+ record = ::On::Destroy.create(name: "Alice")
1299
+ record.update_attributes(name: "blah")
1300
+ record.destroy
1301
+ expect(record.versions.length).to(eq(1))
1302
+ expect(record.versions.last.event).to(eq("destroy"))
1303
+ end
1304
+ end
1305
+
1306
+ context "on []" do
1307
+ before do
1308
+ @record = ::On::EmptyArray.create(name: "Alice")
1309
+ @record.update_attributes(name: "blah")
1310
+ end
1311
+
1312
+ after { @record.destroy }
1313
+
1314
+ it "not have any versions" do
1315
+ expect(@record.versions.length).to(eq(0))
1316
+ end
1317
+
1318
+ it "still respond to touch_with_version" do
1319
+ @record.paper_trail.touch_with_version
1320
+ expect(@record.versions.length).to(eq(1))
1321
+ end
1322
+ end
1323
+
1324
+ context "allows a symbol to be passed" do
1325
+ it "only have a version for hte create event" do
1326
+ record = ::On::Create.create(name: "Alice")
1327
+ record.update_attributes(name: "blah")
1328
+ record.destroy
1329
+ expect(record.versions.length).to(eq(1))
1330
+ expect(record.versions.last.event).to(eq("create"))
1331
+ end
1332
+ end
1333
+ end
1334
+
1335
+ context "A model with column version and custom version_method" do
1336
+ before do
1337
+ @legacy_widget = LegacyWidget.create(name: "foo", version: 2)
1338
+ end
1339
+
1340
+ it "set version on create" do
1341
+ expect(@legacy_widget.version).to(eq(2))
1342
+ end
1343
+
1344
+ it "allow version updates" do
1345
+ @legacy_widget.update_attributes(version: 3)
1346
+ expect(@legacy_widget.version).to(eq(3))
1347
+ end
1348
+
1349
+ it "create a new version record" do
1350
+ expect(@legacy_widget.versions.size).to(eq(1))
1351
+ end
1352
+ end
1353
+
1354
+ context "A reified item with a column -version- and custom version_method" do
1355
+ before do
1356
+ widget = LegacyWidget.create(name: "foo", version: 2)
1357
+ %w[bar baz].each { |name| widget.update_attributes(name: name) }
1358
+ @version = widget.versions.last
1359
+ @widget = @version.reify
1360
+ end
1361
+
1362
+ it "know which version it came from" do
1363
+ expect(@widget.custom_version).to(eq(@version))
1364
+ end
1365
+
1366
+ it "return its previous self" do
1367
+ expect(@widget.paper_trail.previous_version).to(eq(@widget.versions[-2].reify))
1368
+ end
1369
+ end
1370
+
1371
+ context "custom events" do
1372
+ context "on create" do
1373
+ it "only have a version for the created event" do
1374
+ record = ::On::Create.new.tap { |model| model.paper_trail_event = "created" }
1375
+ record.update_attributes(name: "blah")
1376
+ record.destroy
1377
+ expect(record.versions.length).to(eq(1))
1378
+ expect(record.versions.last.event).to(eq("created"))
1379
+ end
1380
+ end
1381
+
1382
+ context "on update" do
1383
+ it "only have a version for the name_updated event" do
1384
+ record = ::On::Update.create(name: "Alice").tap do |model|
1385
+ model.paper_trail_event = "name_updated"
1386
+ end
1387
+ record.update_attributes(name: "blah")
1388
+ record.destroy
1389
+ expect(record.versions.length).to(eq(1))
1390
+ expect(record.versions.last.event).to(eq("name_updated"))
1391
+ end
1392
+ end
1393
+
1394
+ context "on destroy" do
1395
+ it "only have a version for the destroy event" do
1396
+ record = ::On::Destroy.create(name: "Alice").tap do |model|
1397
+ model.paper_trail_event = "destroyed"
1398
+ end
1399
+ record.update_attributes(name: "blah")
1400
+ record.destroy
1401
+ expect(record.versions.length).to(eq(1))
1402
+ expect(record.versions.last.event).to(eq("destroyed"))
1403
+ end
1404
+ end
1405
+ end
1406
+
1407
+ context "`PaperTrail::Config.version_limit` set" do
1408
+ before do
1409
+ PaperTrail.config.version_limit = 2
1410
+ @widget = Widget.create!(name: "Henry")
1411
+ 6.times { @widget.update_attribute(:name, FFaker::Lorem.word) }
1412
+ end
1413
+
1414
+ after { PaperTrail.config.version_limit = nil }
1415
+
1416
+ it "limit the number of versions to 3 (2 plus the created at event)" do
1417
+ expect(@widget.versions.first.event).to(eq("create"))
1418
+ expect(@widget.versions.size).to(eq(3))
1419
+ end
1420
+ end
1421
+ end