paper_trail 7.0.2 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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