mongoid-history 0.8.3 → 0.8.5
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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -1
- data/.document +5 -5
- data/.github/workflows/test.yml +72 -0
- data/.gitignore +46 -46
- data/.rspec +2 -2
- data/.rubocop.yml +6 -6
- data/.rubocop_todo.yml +99 -99
- data/CHANGELOG.md +173 -163
- data/CONTRIBUTING.md +117 -118
- data/Dangerfile +1 -1
- data/Gemfile +49 -40
- data/LICENSE.txt +20 -20
- data/README.md +609 -608
- data/RELEASING.md +66 -67
- data/Rakefile +24 -24
- data/UPGRADING.md +53 -53
- data/lib/mongoid/history/attributes/base.rb +72 -72
- data/lib/mongoid/history/attributes/create.rb +45 -45
- data/lib/mongoid/history/attributes/destroy.rb +34 -34
- data/lib/mongoid/history/attributes/update.rb +104 -104
- data/lib/mongoid/history/options.rb +177 -177
- data/lib/mongoid/history/trackable.rb +588 -583
- data/lib/mongoid/history/tracker.rb +247 -247
- data/lib/mongoid/history/version.rb +5 -5
- data/lib/mongoid/history.rb +77 -77
- data/lib/mongoid-history.rb +1 -1
- data/mongoid-history.gemspec +25 -25
- data/perf/benchmark_modified_attributes_for_create.rb +65 -65
- data/perf/gc_suite.rb +21 -21
- data/spec/integration/embedded_in_polymorphic_spec.rb +112 -112
- data/spec/integration/integration_spec.rb +976 -976
- data/spec/integration/multi_relation_spec.rb +47 -47
- data/spec/integration/multiple_trackers_spec.rb +68 -68
- data/spec/integration/nested_embedded_documents_spec.rb +64 -64
- data/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb +124 -124
- data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +115 -115
- data/spec/integration/subclasses_spec.rb +47 -47
- data/spec/integration/track_history_order_spec.rb +84 -84
- data/spec/integration/validation_failure_spec.rb +76 -76
- data/spec/spec_helper.rb +32 -30
- data/spec/support/error_helpers.rb +7 -0
- data/spec/support/mongoid.rb +11 -11
- data/spec/support/mongoid_history.rb +12 -12
- data/spec/unit/attributes/base_spec.rb +141 -141
- data/spec/unit/attributes/create_spec.rb +342 -342
- data/spec/unit/attributes/destroy_spec.rb +228 -228
- data/spec/unit/attributes/update_spec.rb +342 -342
- data/spec/unit/callback_options_spec.rb +165 -165
- data/spec/unit/embedded_methods_spec.rb +87 -87
- data/spec/unit/history_spec.rb +58 -58
- data/spec/unit/my_instance_methods_spec.rb +555 -555
- data/spec/unit/options_spec.rb +365 -365
- data/spec/unit/singleton_methods_spec.rb +406 -406
- data/spec/unit/store/default_store_spec.rb +11 -11
- data/spec/unit/store/request_store_spec.rb +13 -13
- data/spec/unit/trackable_spec.rb +1057 -987
- data/spec/unit/tracker_spec.rb +190 -190
- metadata +9 -7
- data/.travis.yml +0 -36
data/spec/unit/trackable_spec.rb
CHANGED
@@ -1,987 +1,1057 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Mongoid::History::Trackable do
|
4
|
-
before :each do
|
5
|
-
class MyModel
|
6
|
-
include Mongoid::Document
|
7
|
-
include Mongoid::History::Trackable
|
8
|
-
|
9
|
-
field :foo
|
10
|
-
end
|
11
|
-
|
12
|
-
class MyDynamicModel
|
13
|
-
include Mongoid::Document
|
14
|
-
include Mongoid::History::Trackable
|
15
|
-
include Mongoid::Attributes::Dynamic unless Mongoid::Compatibility::Version.mongoid3?
|
16
|
-
end
|
17
|
-
|
18
|
-
class
|
19
|
-
include Mongoid::
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
it 'should
|
159
|
-
expect(MyModel.
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
describe '#
|
164
|
-
it 'should
|
165
|
-
expect(MyModel.
|
166
|
-
end
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
MyModel.
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
MyModel.
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
MyModel.
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
it_behaves_like 'history tracking'
|
518
|
-
end
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
end
|
550
|
-
|
551
|
-
it 'should
|
552
|
-
|
553
|
-
expect(
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
end
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
end
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
let(:changes) do
|
791
|
-
{ 'emb_ones' => [[{ 'em_foo' => 'Foo'
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
it {
|
811
|
-
end
|
812
|
-
end
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
end
|
973
|
-
end
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongoid::History::Trackable do
|
4
|
+
before :each do
|
5
|
+
class MyModel
|
6
|
+
include Mongoid::Document
|
7
|
+
include Mongoid::History::Trackable
|
8
|
+
|
9
|
+
field :foo
|
10
|
+
end
|
11
|
+
|
12
|
+
class MyDynamicModel
|
13
|
+
include Mongoid::Document
|
14
|
+
include Mongoid::History::Trackable
|
15
|
+
include Mongoid::Attributes::Dynamic unless Mongoid::Compatibility::Version.mongoid3?
|
16
|
+
end
|
17
|
+
|
18
|
+
class MyDeeplyNestedModel
|
19
|
+
include Mongoid::Document
|
20
|
+
include Mongoid::History::Trackable
|
21
|
+
|
22
|
+
embeds_many :children, class_name: 'MyNestableModel', cascade_callbacks: true # The problem only occurs if callbacks are cascaded
|
23
|
+
accepts_nested_attributes_for :children, allow_destroy: true
|
24
|
+
track_history modifier_field: nil
|
25
|
+
end
|
26
|
+
|
27
|
+
class MyNestableModel
|
28
|
+
include Mongoid::Document
|
29
|
+
include Mongoid::History::Trackable
|
30
|
+
|
31
|
+
embedded_in :parent, class_name: 'MyDeeplyNestedModel'
|
32
|
+
embeds_many :children, class_name: 'MyNestableModel', cascade_callbacks: true
|
33
|
+
accepts_nested_attributes_for :children, allow_destroy: true
|
34
|
+
field :name, type: String
|
35
|
+
track_history modifier_field: nil
|
36
|
+
end
|
37
|
+
|
38
|
+
class HistoryTracker
|
39
|
+
include Mongoid::History::Tracker
|
40
|
+
end
|
41
|
+
|
42
|
+
class User
|
43
|
+
include Mongoid::Document
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
after :each do
|
48
|
+
Object.send(:remove_const, :MyModel)
|
49
|
+
Object.send(:remove_const, :MyDynamicModel)
|
50
|
+
Object.send(:remove_const, :HistoryTracker)
|
51
|
+
Object.send(:remove_const, :User)
|
52
|
+
Object.send(:remove_const, :MyDeeplyNestedModel)
|
53
|
+
Object.send(:remove_const, :MyNestableModel)
|
54
|
+
end
|
55
|
+
|
56
|
+
let(:user) { User.create! }
|
57
|
+
|
58
|
+
it 'should have #track_history' do
|
59
|
+
expect(MyModel).to respond_to :track_history
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#track_history' do
|
63
|
+
before :each do
|
64
|
+
class MyModelWithNoModifier
|
65
|
+
include Mongoid::Document
|
66
|
+
include Mongoid::History::Trackable
|
67
|
+
|
68
|
+
field :foo
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
after :each do
|
73
|
+
Object.send(:remove_const, :MyModelWithNoModifier)
|
74
|
+
end
|
75
|
+
|
76
|
+
before :each do
|
77
|
+
MyModel.track_history
|
78
|
+
MyModelWithNoModifier.track_history modifier_field: nil
|
79
|
+
end
|
80
|
+
|
81
|
+
let(:expected_option) do
|
82
|
+
{
|
83
|
+
on: %i[foo],
|
84
|
+
except: %w[created_at updated_at],
|
85
|
+
tracker_class_name: nil,
|
86
|
+
modifier_field: :modifier,
|
87
|
+
version_field: :version,
|
88
|
+
changes_method: :changes,
|
89
|
+
scope: :my_model,
|
90
|
+
track_create: true,
|
91
|
+
track_update: true,
|
92
|
+
track_destroy: true,
|
93
|
+
fields: %w[foo],
|
94
|
+
relations: { embeds_one: {}, embeds_many: {} },
|
95
|
+
dynamic: [],
|
96
|
+
format: {}
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
let(:regular_fields) { ['foo'] }
|
101
|
+
let(:reserved_fields) { %w[_id version modifier_id] }
|
102
|
+
|
103
|
+
it 'should have default options' do
|
104
|
+
expect(MyModel.mongoid_history_options.prepared).to eq(expected_option)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should define callback function #track_update' do
|
108
|
+
expect(MyModel.new.private_methods.collect(&:to_sym)).to include(:track_update)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should define callback function #track_create' do
|
112
|
+
expect(MyModel.new.private_methods.collect(&:to_sym)).to include(:track_create)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should define callback function #track_destroy' do
|
116
|
+
expect(MyModel.new.private_methods.collect(&:to_sym)).to include(:track_destroy)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should define #history_trackable_options' do
|
120
|
+
expect(MyModel.history_trackable_options).to eq(expected_option)
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#modifier' do
|
124
|
+
context 'modifier_field set to nil' do
|
125
|
+
it 'should not have a modifier relationship' do
|
126
|
+
expect(MyModelWithNoModifier.reflect_on_association(:modifier)).to be_nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'modifier_field_optional true' do
|
131
|
+
before :each do
|
132
|
+
class MyModelWithOptionalModifier
|
133
|
+
include Mongoid::Document
|
134
|
+
include Mongoid::History::Trackable
|
135
|
+
|
136
|
+
field :foo
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
after :each do
|
141
|
+
Object.send(:remove_const, :MyModelWithOptionalModifier)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'marks modifier relationship optional' do
|
145
|
+
MyModelWithOptionalModifier.track_history modifier_field_optional: true
|
146
|
+
if Mongoid::Compatibility::Version.mongoid7_or_newer?
|
147
|
+
expect(MyModelWithOptionalModifier.reflect_on_association(:modifier).options[:optional]).to be true
|
148
|
+
elsif Mongoid::Compatibility::Version.mongoid6_or_newer?
|
149
|
+
expect(MyModelWithOptionalModifier.reflect_on_association(:modifier)[:optional]).to be true
|
150
|
+
else
|
151
|
+
expect(MyModelWithOptionalModifier.reflect_on_association(:modifier)).not_to be_nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#tracked_fields' do
|
158
|
+
it 'should return the tracked field list' do
|
159
|
+
expect(MyModel.tracked_fields).to eq(regular_fields)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe '#reserved_tracked_fields' do
|
164
|
+
it 'should return the protected field list' do
|
165
|
+
expect(MyModel.reserved_tracked_fields).to eq(reserved_fields)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should not include modifier_field if not specified' do
|
169
|
+
expect(MyModelWithNoModifier.reserved_tracked_fields).not_to include('modifier')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#tracked_fields_for_action' do
|
174
|
+
it 'should include the reserved fields for destroy' do
|
175
|
+
expect(MyModel.tracked_fields_for_action(:destroy)).to eq(regular_fields + reserved_fields)
|
176
|
+
end
|
177
|
+
it 'should not include the reserved fields for update' do
|
178
|
+
expect(MyModel.tracked_fields_for_action(:update)).to eq(regular_fields)
|
179
|
+
end
|
180
|
+
it 'should not include the reserved fields for create' do
|
181
|
+
expect(MyModel.tracked_fields_for_action(:create)).to eq(regular_fields)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#tracked_field?' do
|
186
|
+
it 'should not include the reserved fields by default' do
|
187
|
+
expect(MyModel.tracked_field?(:_id)).to be false
|
188
|
+
end
|
189
|
+
it 'should include the reserved fields for destroy' do
|
190
|
+
expect(MyModel.tracked_field?(:_id, :destroy)).to be true
|
191
|
+
end
|
192
|
+
it 'should allow field aliases' do
|
193
|
+
expect(MyModel.tracked_field?(:id, :destroy)).to be true
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'when model is dynamic' do
|
197
|
+
it 'should allow dynamic fields tracking' do
|
198
|
+
MyDynamicModel.track_history
|
199
|
+
expect(MyDynamicModel.tracked_field?(:dynamic_field, :destroy)).to be true
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
unless Mongoid::Compatibility::Version.mongoid3?
|
204
|
+
context 'when model is not dynamic' do
|
205
|
+
it 'should not allow dynamic fields tracking' do
|
206
|
+
MyModel.track_history
|
207
|
+
expect(MyModel.tracked_field?(:dynamic_field, :destroy)).to be false
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'allows a non-database field to be specified' do
|
213
|
+
class MyNonDatabaseModel
|
214
|
+
include Mongoid::Document
|
215
|
+
include Mongoid::History::Trackable
|
216
|
+
track_history on: ['baz']
|
217
|
+
end
|
218
|
+
|
219
|
+
expect(MyNonDatabaseModel.tracked_field?(:baz)).to be true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context '#dynamic_field?' do
|
224
|
+
context 'when model is dynamic' do
|
225
|
+
it 'should return true' do
|
226
|
+
MyDynamicModel.track_history
|
227
|
+
expect(MyDynamicModel.dynamic_field?(:dynamic_field)).to be true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
unless Mongoid::Compatibility::Version.mongoid3?
|
232
|
+
context 'when model is not dynamic' do
|
233
|
+
it 'should return false' do
|
234
|
+
MyModel.track_history
|
235
|
+
expect(MyModel.dynamic_field?(:dynamic_field)).to be false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe '#field_format' do
|
242
|
+
before :each do
|
243
|
+
class ModelOne
|
244
|
+
include Mongoid::Document
|
245
|
+
include Mongoid::History::Trackable
|
246
|
+
|
247
|
+
field :foo
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
after :each do
|
252
|
+
Object.send(:remove_const, :ModelOne)
|
253
|
+
end
|
254
|
+
|
255
|
+
let(:format) { '***' }
|
256
|
+
|
257
|
+
before do
|
258
|
+
ModelOne.track_history format: { foo: format }
|
259
|
+
end
|
260
|
+
|
261
|
+
context 'when field is formatted' do
|
262
|
+
it 'should return the format' do
|
263
|
+
expect(ModelOne.field_format(:foo)).to be format
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'when field is not formatted' do
|
268
|
+
it 'should return nil' do
|
269
|
+
expect(ModelOne.field_format(:bar)).to be_nil
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'sub-model' do
|
275
|
+
before :each do
|
276
|
+
class MySubModel < MyModel
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
after :each do
|
281
|
+
Object.send(:remove_const, :MySubModel)
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'should have default options' do
|
285
|
+
expect(MyModel.mongoid_history_options.prepared).to eq(expected_option)
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should define #history_trackable_options' do
|
289
|
+
expect(MySubModel.history_trackable_options).to eq(expected_option)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe '#track_history?' do
|
294
|
+
shared_examples_for 'history tracking' do
|
295
|
+
after do
|
296
|
+
Mongoid::History.store[Mongoid::History::GLOBAL_TRACK_HISTORY_FLAG] = true
|
297
|
+
Mongoid::History.store[MyModel.track_history_flag] = true
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'when tracking is globally enabled' do
|
301
|
+
it 'should be enabled on the current thread' do
|
302
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
303
|
+
expect(MyModel.new.track_history?).to eq(true)
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should be disabled within disable_tracking' do
|
307
|
+
MyModel.disable_tracking do
|
308
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
309
|
+
expect(MyModel.new.track_history?).to eq(false)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'should be enabled within enable_tracking' do
|
314
|
+
MyModel.disable_tracking do
|
315
|
+
MyModel.enable_tracking do
|
316
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
317
|
+
expect(MyModel.new.track_history?).to eq(true)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should still be disabled after completing a nested disable_tracking' do
|
323
|
+
MyModel.disable_tracking do
|
324
|
+
MyModel.disable_tracking {}
|
325
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
326
|
+
expect(MyModel.new.track_history?).to eq(false)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'should still be enabled after completing a nested enable_tracking' do
|
331
|
+
MyModel.enable_tracking do
|
332
|
+
MyModel.enable_tracking {}
|
333
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
334
|
+
expect(MyModel.new.track_history?).to eq(true)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'should restore the original state after completing enable_tracking' do
|
339
|
+
MyModel.disable_tracking do
|
340
|
+
MyModel.enable_tracking {}
|
341
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
342
|
+
expect(MyModel.new.track_history?).to eq(false)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'should be rescued if an exception occurs in disable_tracking' do
|
347
|
+
ignore_errors { MyModel.disable_tracking { raise 'exception' } }
|
348
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
349
|
+
expect(MyModel.new.track_history?).to eq(true)
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should be rescued if an exception occurs in enable_tracking' do
|
353
|
+
MyModel.disable_tracking do
|
354
|
+
ignore_errors { MyModel.enable_tracking { raise 'exception' } }
|
355
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
356
|
+
expect(MyModel.new.track_history?).to eq(false)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'should stay disabled if disable_tracking called without a block' do
|
361
|
+
MyModel.disable_tracking!
|
362
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
363
|
+
expect(MyModel.new.track_history?).to eq(false)
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'should stay enabled if enable_tracking called without a block' do
|
367
|
+
MyModel.disable_tracking do
|
368
|
+
MyModel.enable_tracking!
|
369
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
370
|
+
expect(MyModel.new.track_history?).to eq(true)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'with multiple classes' do
|
375
|
+
before :each do
|
376
|
+
class MyModel2
|
377
|
+
include Mongoid::Document
|
378
|
+
include Mongoid::History::Trackable
|
379
|
+
|
380
|
+
track_history
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
after :each do
|
385
|
+
Object.send(:remove_const, :MyModel2)
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should be disabled only for the class that calls disable_tracking' do
|
389
|
+
MyModel.disable_tracking do
|
390
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
391
|
+
expect(MyModel2.new.track_history?).to eq(true)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
context 'when changing global tracking' do
|
398
|
+
it 'should be disabled by the global disablement' do
|
399
|
+
Mongoid::History.disable do
|
400
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
401
|
+
expect(MyModel.new.track_history?).to eq(false)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'should be enabled by the global enablement' do
|
406
|
+
Mongoid::History.disable do
|
407
|
+
Mongoid::History.enable do
|
408
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
409
|
+
expect(MyModel.new.track_history?).to eq(true)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'should restore the original state after completing enable' do
|
415
|
+
Mongoid::History.disable do
|
416
|
+
Mongoid::History.enable {}
|
417
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
418
|
+
expect(MyModel.new.track_history?).to eq(false)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'should still be disabled after completing a nested disable' do
|
423
|
+
Mongoid::History.disable do
|
424
|
+
Mongoid::History.disable {}
|
425
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
426
|
+
expect(MyModel.new.track_history?).to eq(false)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'should still be enabled after completing a nested enable' do
|
431
|
+
Mongoid::History.disable do
|
432
|
+
Mongoid::History.enable do
|
433
|
+
Mongoid::History.enable {}
|
434
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
435
|
+
expect(MyModel.new.track_history?).to eq(true)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'should be disabled within disable_tracking' do
|
441
|
+
Mongoid::History.disable do
|
442
|
+
MyModel.disable_tracking do
|
443
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
444
|
+
expect(MyModel.new.track_history?).to eq(false)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'should be rescued if an exception occurs in disable' do
|
450
|
+
Mongoid::History.disable do
|
451
|
+
ignore_errors { MyModel.disable_tracking { raise 'exception' } }
|
452
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
453
|
+
expect(MyModel.new.track_history?).to eq(false)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'should be rescued if an exception occurs in enable' do
|
458
|
+
Mongoid::History.disable do
|
459
|
+
ignore_errors { Mongoid::History.enable { raise 'exception' } }
|
460
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
461
|
+
expect(MyModel.new.track_history?).to eq(false)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'should stay disabled if disable called without a block' do
|
466
|
+
Mongoid::History.disable!
|
467
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
468
|
+
expect(MyModel.new.track_history?).to eq(false)
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'should stay enabled if enable called without a block' do
|
472
|
+
Mongoid::History.disable do
|
473
|
+
Mongoid::History.enable!
|
474
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
475
|
+
expect(MyModel.new.track_history?).to eq(true)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
context 'with multiple classes' do
|
480
|
+
before :each do
|
481
|
+
class MyModel2
|
482
|
+
include Mongoid::Document
|
483
|
+
include Mongoid::History::Trackable
|
484
|
+
|
485
|
+
track_history
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
after :each do
|
490
|
+
Object.send(:remove_const, :MyModel2)
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'should be disabled for all classes' do
|
494
|
+
Mongoid::History.disable do
|
495
|
+
MyModel.disable_tracking do
|
496
|
+
expect(Mongoid::History.enabled?).to eq(false)
|
497
|
+
expect(MyModel2.new.track_history?).to eq(false)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'should rescue errors through both local and global tracking scopes' do
|
505
|
+
ignore_errors { Mongoid::History.disable { MyModel.disable_tracking { raise 'exception' } } }
|
506
|
+
expect(Mongoid::History.enabled?).to eq(true)
|
507
|
+
expect(MyModel.new.track_history?).to eq(true)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
context 'when store is Thread' do
|
512
|
+
it_behaves_like 'history tracking'
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'when store is RequestStore' do
|
516
|
+
before { stub_const('RequestStore', RequestStoreTemp) }
|
517
|
+
it_behaves_like 'history tracking'
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
describe ':changes_method' do
|
522
|
+
it 'should be set in parent class' do
|
523
|
+
expect(MyModel.history_trackable_options[:changes_method]).to eq :changes
|
524
|
+
end
|
525
|
+
|
526
|
+
context 'subclass' do
|
527
|
+
before :each do
|
528
|
+
# BUGBUG: if this is not prepared, it inherits the subclass settings
|
529
|
+
MyModel.history_trackable_options
|
530
|
+
|
531
|
+
class CustomTracker < MyModel
|
532
|
+
field :key
|
533
|
+
|
534
|
+
track_history on: :key, changes_method: :my_changes, track_create: true
|
535
|
+
|
536
|
+
def my_changes
|
537
|
+
changes.merge('key' => ["Save history-#{key}", "Save history-#{key}"])
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
after :each do
|
543
|
+
Object.send(:remove_const, :CustomTracker)
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'should not override in parent class' do
|
547
|
+
expect(MyModel.history_trackable_options[:changes_method]).to eq :changes
|
548
|
+
expect(CustomTracker.history_trackable_options[:changes_method]).to eq :my_changes
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'should default to :changes' do
|
552
|
+
m = MyModel.create!(modifier: user)
|
553
|
+
expect(m).to receive(:changes).exactly(3).times.and_call_original
|
554
|
+
expect(m).not_to receive(:my_changes)
|
555
|
+
m.save!
|
556
|
+
end
|
557
|
+
|
558
|
+
context 'with another model' do
|
559
|
+
before :each do
|
560
|
+
class MyModel3 < MyModel
|
561
|
+
track_history changes_method: :my_changes
|
562
|
+
|
563
|
+
def my_changes
|
564
|
+
{}
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
after :each do
|
570
|
+
Object.send(:remove_const, :MyModel3)
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'should allow an alternate method to be specified' do
|
574
|
+
m = MyModel3.create!(modifier: user)
|
575
|
+
expect(m).to receive(:changes).twice.and_call_original
|
576
|
+
expect(m).to receive(:my_changes).once.and_call_original
|
577
|
+
m.save
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
it 'should allow an alternate method to be specified on object creation' do
|
582
|
+
m = if Mongoid::Compatibility::Version.mongoid7_or_newer? # BUGBUG
|
583
|
+
CustomTracker.create!(key: 'on object creation', modifier: user)
|
584
|
+
else
|
585
|
+
CustomTracker.create!(key: 'on object creation')
|
586
|
+
end
|
587
|
+
history_track = m.history_tracks.last
|
588
|
+
expect(history_track.modified['key']).to eq('Save history-on object creation')
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
describe '#history_settings' do
|
595
|
+
before(:each) { Mongoid::History.trackable_settings = nil }
|
596
|
+
|
597
|
+
before :each do
|
598
|
+
class ModelOne
|
599
|
+
include Mongoid::Document
|
600
|
+
include Mongoid::History::Trackable
|
601
|
+
|
602
|
+
store_in collection: :model_ones
|
603
|
+
|
604
|
+
if Mongoid::Compatibility::Version.mongoid7_or_newer?
|
605
|
+
embeds_one :emb_one
|
606
|
+
embeds_many :emb_twos
|
607
|
+
else
|
608
|
+
embeds_one :emb_one, inverse_class_name: 'EmbOne'
|
609
|
+
embeds_many :emb_twos, inverse_class_name: 'EmbTwo'
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
class EmbOne
|
614
|
+
include Mongoid::Document
|
615
|
+
include Mongoid::History::Trackable
|
616
|
+
|
617
|
+
embedded_in :model_one
|
618
|
+
end
|
619
|
+
|
620
|
+
class EmbTwo
|
621
|
+
include Mongoid::Document
|
622
|
+
include Mongoid::History::Trackable
|
623
|
+
|
624
|
+
embedded_in :model_one
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
after :each do
|
629
|
+
Object.send(:remove_const, :ModelOne)
|
630
|
+
Object.send(:remove_const, :EmbOne)
|
631
|
+
Object.send(:remove_const, :EmbTwo)
|
632
|
+
end
|
633
|
+
|
634
|
+
let(:default_options) { { paranoia_field: 'deleted_at' } }
|
635
|
+
|
636
|
+
context 'when options not passed' do
|
637
|
+
before(:each) do
|
638
|
+
ModelOne.history_settings
|
639
|
+
EmbOne.history_settings
|
640
|
+
EmbTwo.history_settings
|
641
|
+
end
|
642
|
+
|
643
|
+
it 'should use default options' do
|
644
|
+
expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(default_options)
|
645
|
+
expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(default_options)
|
646
|
+
expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(default_options)
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
context 'when extra invalid options passed' do
|
651
|
+
before(:each) do
|
652
|
+
ModelOne.history_settings foo: :bar
|
653
|
+
EmbOne.history_settings em_foo: :em_bar
|
654
|
+
EmbTwo.history_settings em_foo: :em_baz
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'should ignore invalid options' do
|
658
|
+
expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(default_options)
|
659
|
+
expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(default_options)
|
660
|
+
expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(default_options)
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
context 'when valid options passed' do
|
665
|
+
before(:each) do
|
666
|
+
ModelOne.history_settings paranoia_field: :disabled_at
|
667
|
+
EmbOne.history_settings paranoia_field: :deactivated_at
|
668
|
+
EmbTwo.history_settings paranoia_field: :omitted_at
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'should override default options' do
|
672
|
+
expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(paranoia_field: 'disabled_at')
|
673
|
+
expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(paranoia_field: 'deactivated_at')
|
674
|
+
expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(paranoia_field: 'omitted_at')
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
context 'when string keys' do
|
679
|
+
before(:each) { ModelOne.history_settings 'paranoia_field' => 'erased_at' }
|
680
|
+
|
681
|
+
it 'should convert option keys to symbols' do
|
682
|
+
expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(paranoia_field: 'erased_at')
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
context 'when paranoia field has alias' do
|
687
|
+
before :each do
|
688
|
+
class ModelTwo
|
689
|
+
include Mongoid::Document
|
690
|
+
include Mongoid::History::Trackable
|
691
|
+
|
692
|
+
field :nglt, as: :neglected_at
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
after(:each) { Object.send(:remove_const, :ModelTwo) }
|
697
|
+
|
698
|
+
before(:each) { ModelTwo.history_settings paranoia_field: :neglected_at }
|
699
|
+
|
700
|
+
it { expect(Mongoid::History.trackable_settings[:ModelTwo]).to eq(paranoia_field: 'nglt') }
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
describe '#tracker_class' do
|
705
|
+
before :each do
|
706
|
+
class MyTrackerClass
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
after(:each) { Object.send(:remove_const, :MyTrackerClass) }
|
711
|
+
|
712
|
+
context 'when options contain tracker_class_name' do
|
713
|
+
context 'when underscored' do
|
714
|
+
before { MyModel.track_history tracker_class_name: 'my_tracker_class' }
|
715
|
+
it { expect(MyModel.tracker_class).to eq MyTrackerClass }
|
716
|
+
end
|
717
|
+
|
718
|
+
context 'when camelcased' do
|
719
|
+
before { MyModel.track_history tracker_class_name: 'MyTrackerClass' }
|
720
|
+
it { expect(MyModel.tracker_class).to eq MyTrackerClass }
|
721
|
+
end
|
722
|
+
|
723
|
+
context 'when constant' do
|
724
|
+
before { MyModel.track_history tracker_class_name: MyTrackerClass }
|
725
|
+
it { expect(MyModel.tracker_class).to eq MyTrackerClass }
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
describe '#modified_attributes_for_update' do
|
730
|
+
before :each do
|
731
|
+
class ModelOne
|
732
|
+
include Mongoid::Document
|
733
|
+
include Mongoid::History::Trackable
|
734
|
+
|
735
|
+
store_in collection: :model_ones
|
736
|
+
field :foo
|
737
|
+
|
738
|
+
if Mongoid::Compatibility::Version.mongoid7_or_newer?
|
739
|
+
embeds_many :emb_ones
|
740
|
+
else
|
741
|
+
embeds_many :emb_ones, inverse_class_name: 'EmbOne'
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
class EmbOne
|
746
|
+
include Mongoid::Document
|
747
|
+
include Mongoid::History::Trackable
|
748
|
+
|
749
|
+
field :em_foo
|
750
|
+
embedded_in :model_one
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
after :each do
|
755
|
+
Object.send(:remove_const, :ModelOne)
|
756
|
+
Object.send(:remove_const, :EmbOne)
|
757
|
+
end
|
758
|
+
|
759
|
+
before(:each) { model_one.save! }
|
760
|
+
|
761
|
+
let(:model_one) { ModelOne.new(foo: 'Foo') }
|
762
|
+
let(:changes) { {} }
|
763
|
+
subject { model_one.send(:modified_attributes_for_update) }
|
764
|
+
|
765
|
+
describe 'embeds_many' do
|
766
|
+
before(:each) { allow(model_one).to receive(:changes) { changes } }
|
767
|
+
|
768
|
+
context 'when not paranoia' do
|
769
|
+
before(:each) { ModelOne.track_history(on: :emb_ones, modifier_field_optional: true) }
|
770
|
+
let(:changes) { { 'emb_ones' => [[{ 'em_foo' => 'Foo' }], [{ 'em_foo' => 'Foo-new' }]] } }
|
771
|
+
it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo' }] }
|
772
|
+
it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-new' }] }
|
773
|
+
end
|
774
|
+
|
775
|
+
context 'when default field for paranoia' do
|
776
|
+
before(:each) { ModelOne.track_history(on: :emb_ones, modifier_field_optional: true) }
|
777
|
+
let(:changes) do
|
778
|
+
{ 'emb_ones' => [[{ 'em_foo' => 'Foo' }, { 'em_foo' => 'Foo-2', 'deleted_at' => Time.now }],
|
779
|
+
[{ 'em_foo' => 'Foo-new' }, { 'em_foo' => 'Foo-2-new', 'deleted_at' => Time.now }]] }
|
780
|
+
end
|
781
|
+
it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo' }] }
|
782
|
+
it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-new' }] }
|
783
|
+
end
|
784
|
+
|
785
|
+
context 'when custom field for paranoia' do
|
786
|
+
before(:each) do
|
787
|
+
ModelOne.track_history on: :emb_ones, modifier_field_optional: true
|
788
|
+
EmbOne.history_settings paranoia_field: :my_paranoia_field
|
789
|
+
end
|
790
|
+
let(:changes) do
|
791
|
+
{ 'emb_ones' => [[{ 'em_foo' => 'Foo', 'my_paranoia_field' => Time.now },
|
792
|
+
{ 'em_foo' => 'Foo-2' }],
|
793
|
+
[{ 'em_foo' => 'Foo-new', 'my_paranoia_field' => Time.now },
|
794
|
+
{ 'em_foo' => 'Foo-2-new' }]] }
|
795
|
+
end
|
796
|
+
it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo-2' }] }
|
797
|
+
it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-2-new' }] }
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
describe 'fields' do
|
802
|
+
context 'when custom method for changes' do
|
803
|
+
before(:each) do
|
804
|
+
ModelOne.track_history(on: :foo, changes_method: :my_changes_method)
|
805
|
+
allow(ModelOne).to receive(:dynamic_enabled?) { false }
|
806
|
+
allow(model_one).to receive(:my_changes_method) { changes }
|
807
|
+
end
|
808
|
+
|
809
|
+
let(:changes) { { 'foo' => ['Foo', 'Foo-new'], 'bar' => ['Bar', 'Bar-new'] } }
|
810
|
+
it { is_expected.to eq('foo' => ['Foo', 'Foo-new']) }
|
811
|
+
end
|
812
|
+
end
|
813
|
+
end
|
814
|
+
|
815
|
+
context 'when options not contain tracker_class_name' do
|
816
|
+
before { MyModel.track_history }
|
817
|
+
it { expect(MyModel.tracker_class).to eq Tracker }
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
describe '#track_update' do
|
822
|
+
before(:each) { MyModel.track_history(on: :foo, track_update: true) }
|
823
|
+
|
824
|
+
let!(:m) { MyModel.create!(foo: 'bar', modifier: user) }
|
825
|
+
|
826
|
+
it 'should create history' do
|
827
|
+
expect { m.update_attributes!(foo: 'bar2') }.to change(Tracker, :count).by(1)
|
828
|
+
end
|
829
|
+
|
830
|
+
it 'should not create history when error raised' do
|
831
|
+
expect(m).to receive(:update_attributes!).and_raise(StandardError)
|
832
|
+
expect do
|
833
|
+
expect { m.update_attributes!(foo: 'bar2') }.to raise_error(StandardError)
|
834
|
+
end.to change(Tracker, :count).by(0)
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
describe '#track_destroy' do
|
839
|
+
before(:each) { MyModel.track_history(on: :foo, track_destroy: true) }
|
840
|
+
|
841
|
+
let!(:m) { MyModel.create!(foo: 'bar', modifier: user) }
|
842
|
+
|
843
|
+
it 'should create history' do
|
844
|
+
expect { m.destroy }.to change(Tracker, :count).by(1)
|
845
|
+
end
|
846
|
+
|
847
|
+
it 'should not create history when error raised' do
|
848
|
+
expect(m).to receive(:destroy).and_raise(StandardError)
|
849
|
+
expect do
|
850
|
+
expect { m.destroy }.to raise_error(StandardError)
|
851
|
+
end.to change(Tracker, :count).by(0)
|
852
|
+
end
|
853
|
+
|
854
|
+
context 'with a deeply nested model' do
|
855
|
+
let(:m) do
|
856
|
+
MyDeeplyNestedModel.create!(
|
857
|
+
children: [
|
858
|
+
MyNestableModel.new(
|
859
|
+
name: 'grandparent',
|
860
|
+
children: [
|
861
|
+
MyNestableModel.new(name: 'parent 1', children: [MyNestableModel.new(name: 'child 1')]),
|
862
|
+
MyNestableModel.new(name: 'parent 2', children: [MyNestableModel.new(name: 'child 2')])
|
863
|
+
]
|
864
|
+
)
|
865
|
+
]
|
866
|
+
)
|
867
|
+
end
|
868
|
+
let(:attributes) do
|
869
|
+
{
|
870
|
+
'children_attributes' => [
|
871
|
+
{
|
872
|
+
'id' => m.children[0].id,
|
873
|
+
'children_attributes' => [
|
874
|
+
{ 'id' => m.children[0].children[0].id, '_destroy' => '0' },
|
875
|
+
{ 'id' => m.children[0].children[1].id, '_destroy' => '1' }
|
876
|
+
]
|
877
|
+
}
|
878
|
+
]
|
879
|
+
}
|
880
|
+
end
|
881
|
+
|
882
|
+
subject(:updated) do
|
883
|
+
m.update_attributes attributes
|
884
|
+
m.reload
|
885
|
+
end
|
886
|
+
|
887
|
+
let(:names_of_destroyed) do
|
888
|
+
MyDeeplyNestedModel.tracker_class
|
889
|
+
.where('association_chain.id' => updated.id, 'action' => 'destroy')
|
890
|
+
.map { |track| track.original['name'] }
|
891
|
+
end
|
892
|
+
|
893
|
+
it 'does not corrupt embedded models' do
|
894
|
+
expect(updated.children[0].children.count).to eq 1 # When the problem occurs, the 2nd child will continue to be present, but will only contain the version attribute
|
895
|
+
end
|
896
|
+
|
897
|
+
it 'creates a history track for the doc explicitly destroyed' do
|
898
|
+
expect(names_of_destroyed).to include 'parent 2'
|
899
|
+
end
|
900
|
+
|
901
|
+
it 'creates a history track for the doc implicitly destroyed' do
|
902
|
+
expect(names_of_destroyed).to include 'child 2'
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
context 'with multiple embeds_many models' do
|
907
|
+
let(:m) do
|
908
|
+
MyDeeplyNestedModel.create!(
|
909
|
+
children: [
|
910
|
+
MyNestableModel.new(
|
911
|
+
name: 'parent',
|
912
|
+
children: [
|
913
|
+
MyNestableModel.new(name: 'child 1'),
|
914
|
+
MyNestableModel.new(name: 'child 2'),
|
915
|
+
MyNestableModel.new(name: 'child 3')
|
916
|
+
]
|
917
|
+
)
|
918
|
+
]
|
919
|
+
)
|
920
|
+
end
|
921
|
+
|
922
|
+
let(:attributes) do
|
923
|
+
{
|
924
|
+
'children_attributes' => [
|
925
|
+
{
|
926
|
+
'id' => m.children[0].id,
|
927
|
+
'children_attributes' => [
|
928
|
+
{ 'id' => m.children[0].children[0].id, '_destroy' => '0' },
|
929
|
+
{ 'id' => m.children[0].children[1].id, '_destroy' => '1' },
|
930
|
+
{ 'id' => m.children[0].children[2].id, '_destroy' => '1' }
|
931
|
+
]
|
932
|
+
}
|
933
|
+
]
|
934
|
+
}
|
935
|
+
end
|
936
|
+
|
937
|
+
subject(:updated) do
|
938
|
+
m.update_attributes attributes
|
939
|
+
m.reload
|
940
|
+
end
|
941
|
+
|
942
|
+
it 'does not corrupt the document' do
|
943
|
+
expect(updated.children[0].children.length).to eq(1)
|
944
|
+
end
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
describe '#track_create' do
|
949
|
+
before :each do
|
950
|
+
class MyModelWithNoModifier
|
951
|
+
include Mongoid::Document
|
952
|
+
include Mongoid::History::Trackable
|
953
|
+
|
954
|
+
field :foo
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
after(:each) { Object.send(:remove_const, :MyModelWithNoModifier) }
|
959
|
+
|
960
|
+
before :each do
|
961
|
+
MyModel.track_history(on: :foo, track_create: true)
|
962
|
+
MyModelWithNoModifier.track_history modifier_field: nil
|
963
|
+
end
|
964
|
+
|
965
|
+
it 'should create history' do
|
966
|
+
expect { MyModel.create!(foo: 'bar', modifier: user) }.to change(Tracker, :count).by(1)
|
967
|
+
end
|
968
|
+
|
969
|
+
context 'no modifier_field' do
|
970
|
+
it 'should create history' do
|
971
|
+
expect { MyModelWithNoModifier.create!(foo: 'bar').to change(Tracker, :count).by(1) }
|
972
|
+
end
|
973
|
+
end
|
974
|
+
|
975
|
+
it 'should not create history when error raised' do
|
976
|
+
expect(MyModel).to receive(:create!).and_raise(StandardError)
|
977
|
+
expect do
|
978
|
+
expect { MyModel.create!(foo: 'bar') }.to raise_error(StandardError)
|
979
|
+
end.to change(Tracker, :count).by(0)
|
980
|
+
end
|
981
|
+
end
|
982
|
+
|
983
|
+
context 'changing collection' do
|
984
|
+
before :each do
|
985
|
+
class Fish
|
986
|
+
include Mongoid::Document
|
987
|
+
include Mongoid::History::Trackable
|
988
|
+
|
989
|
+
track_history on: [:species], modifier_field_optional: true
|
990
|
+
store_in collection: :animals
|
991
|
+
|
992
|
+
field :species
|
993
|
+
end
|
994
|
+
end
|
995
|
+
|
996
|
+
after(:each) { Object.send(:remove_const, :Fish) }
|
997
|
+
|
998
|
+
it 'should track history' do
|
999
|
+
expect do
|
1000
|
+
expect { Fish.new.save! }.to_not raise_error
|
1001
|
+
end.to change(Tracker, :count).by(1)
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
context "extending a #{described_class}" do
|
1006
|
+
before :each do
|
1007
|
+
MyModel.track_history
|
1008
|
+
|
1009
|
+
class CustomTracker < MyModel
|
1010
|
+
field :key
|
1011
|
+
|
1012
|
+
track_history on: :key, changes_method: :my_changes, track_create: true
|
1013
|
+
|
1014
|
+
def my_changes
|
1015
|
+
changes.merge('key' => ["Save history-#{key}", "Save history-#{key}"])
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
MyModel.history_trackable_options
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
after(:each) { Object.send(:remove_const, :CustomTracker) }
|
1023
|
+
|
1024
|
+
it 'should not override in parent class' do
|
1025
|
+
expect(MyModel.history_trackable_options[:changes_method]).to eq :changes
|
1026
|
+
expect(CustomTracker.history_trackable_options[:changes_method]).to eq :my_changes
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
it 'should default to :changes' do
|
1030
|
+
m = MyModel.create!(modifier: user)
|
1031
|
+
expect(m).to receive(:changes).exactly(3).times.and_call_original
|
1032
|
+
expect(m).not_to receive(:my_changes)
|
1033
|
+
m.save!
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
context "subclassing a #{described_class}" do
|
1038
|
+
before :each do
|
1039
|
+
MyModel.track_history(track_destroy: false)
|
1040
|
+
|
1041
|
+
class MySubclassModel < MyModel
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
after :each do
|
1046
|
+
Object.send(:remove_const, :MySubclassModel)
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
describe '.inherited' do
|
1050
|
+
it 'creates new history options for the subclass' do
|
1051
|
+
options = MySubclassModel.mongoid_history_options
|
1052
|
+
expect(options.trackable).to eq MySubclassModel
|
1053
|
+
expect(options.options).to eq MyModel.mongoid_history_options.options
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
end
|