mongoid-history 0.8.0 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -1
  3. data/.document +5 -5
  4. data/.github/workflows/test.yml +72 -0
  5. data/.gitignore +46 -46
  6. data/.rspec +2 -2
  7. data/.rubocop.yml +6 -6
  8. data/.rubocop_todo.yml +99 -101
  9. data/CHANGELOG.md +173 -144
  10. data/CONTRIBUTING.md +117 -118
  11. data/Dangerfile +1 -1
  12. data/Gemfile +49 -37
  13. data/LICENSE.txt +20 -20
  14. data/README.md +609 -595
  15. data/RELEASING.md +66 -67
  16. data/Rakefile +24 -24
  17. data/UPGRADING.md +53 -34
  18. data/lib/mongoid/history/attributes/base.rb +72 -72
  19. data/lib/mongoid/history/attributes/create.rb +45 -50
  20. data/lib/mongoid/history/attributes/destroy.rb +34 -34
  21. data/lib/mongoid/history/attributes/update.rb +104 -45
  22. data/lib/mongoid/history/options.rb +177 -179
  23. data/lib/mongoid/history/trackable.rb +588 -521
  24. data/lib/mongoid/history/tracker.rb +247 -244
  25. data/lib/mongoid/history/version.rb +5 -5
  26. data/lib/mongoid/history.rb +77 -52
  27. data/lib/mongoid-history.rb +1 -1
  28. data/mongoid-history.gemspec +25 -25
  29. data/perf/benchmark_modified_attributes_for_create.rb +65 -0
  30. data/perf/gc_suite.rb +21 -0
  31. data/spec/integration/embedded_in_polymorphic_spec.rb +112 -135
  32. data/spec/integration/integration_spec.rb +976 -942
  33. data/spec/integration/multi_relation_spec.rb +47 -53
  34. data/spec/integration/multiple_trackers_spec.rb +68 -71
  35. data/spec/integration/nested_embedded_documents_spec.rb +64 -84
  36. data/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb +124 -0
  37. data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +115 -127
  38. data/spec/integration/subclasses_spec.rb +47 -29
  39. data/spec/integration/track_history_order_spec.rb +84 -52
  40. data/spec/integration/validation_failure_spec.rb +76 -63
  41. data/spec/spec_helper.rb +32 -25
  42. data/spec/support/error_helpers.rb +7 -0
  43. data/spec/support/mongoid.rb +11 -11
  44. data/spec/support/mongoid_history.rb +12 -13
  45. data/spec/unit/attributes/base_spec.rb +141 -150
  46. data/spec/unit/attributes/create_spec.rb +342 -315
  47. data/spec/unit/attributes/destroy_spec.rb +228 -218
  48. data/spec/unit/attributes/update_spec.rb +342 -321
  49. data/spec/unit/callback_options_spec.rb +165 -159
  50. data/spec/unit/embedded_methods_spec.rb +87 -69
  51. data/spec/unit/history_spec.rb +58 -35
  52. data/spec/unit/my_instance_methods_spec.rb +555 -485
  53. data/spec/unit/options_spec.rb +365 -327
  54. data/spec/unit/singleton_methods_spec.rb +406 -338
  55. data/spec/unit/store/default_store_spec.rb +11 -11
  56. data/spec/unit/store/request_store_spec.rb +13 -13
  57. data/spec/unit/trackable_spec.rb +1057 -689
  58. data/spec/unit/tracker_spec.rb +190 -163
  59. metadata +13 -8
  60. data/.travis.yml +0 -35
@@ -1,485 +1,555 @@
1
- require 'spec_helper'
2
-
3
- describe Mongoid::History::Trackable do
4
- describe 'MyInstanceMethods' do
5
- before :all do
6
- ModelOne = Class.new do
7
- include Mongoid::Document
8
- include Mongoid::History::Trackable
9
- store_in collection: :model_ones
10
- field :foo
11
- field :b, as: :bar
12
- embeds_one :emb_one, inverse_class_name: 'EmbOne'
13
- embeds_one :emb_two, store_as: :emt, inverse_class_name: 'EmbTwo'
14
- embeds_many :emb_threes, inverse_class_name: 'EmbThree'
15
- embeds_many :emb_fours, store_as: :emfs, inverse_class_name: 'EmbFour'
16
- end
17
-
18
- EmbOne = Class.new do
19
- include Mongoid::Document
20
- include Mongoid::History::Trackable
21
- field :f_em_foo
22
- field :fmb, as: :f_em_bar
23
- embedded_in :model_one
24
- end
25
-
26
- EmbTwo = Class.new do
27
- include Mongoid::Document
28
- include Mongoid::History::Trackable
29
- field :baz
30
- embedded_in :model_one
31
- end
32
-
33
- EmbThree = Class.new do
34
- include Mongoid::Document
35
- include Mongoid::History::Trackable
36
- field :f_em_foo
37
- field :fmb, as: :f_em_bar
38
- embedded_in :model_one
39
- end
40
-
41
- EmbFour = Class.new do
42
- include Mongoid::Document
43
- include Mongoid::History::Trackable
44
- field :baz
45
- embedded_in :model_one
46
- end
47
-
48
- ModelOne.track_history(on: %i[foo emb_one emb_threes])
49
- @persisted_history_options = Mongoid::History.trackable_class_options
50
- end
51
- before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options }
52
-
53
- let(:bson_class) { defined?(BSON::ObjectId) ? BSON::ObjectId : Moped::BSON::ObjectId }
54
-
55
- let(:emb_one) { EmbOne.new(f_em_foo: 'Foo', f_em_bar: 'Bar') }
56
- let(:emb_threes) { [EmbThree.new(f_em_foo: 'Foo', f_em_bar: 'Bar')] }
57
- let(:model_one) do
58
- ModelOne.new(foo: 'Foo',
59
- bar: 'Bar',
60
- emb_one: emb_one,
61
- emb_threes: emb_threes)
62
- end
63
-
64
- describe '#modified_attributes_for_create' do
65
- before(:each) { ModelOne.clear_trackable_memoization }
66
- subject { model_one.send(:modified_attributes_for_create) }
67
-
68
- context 'with tracked embeds_one object' do
69
- before(:each) { ModelOne.track_history(on: { emb_one: :f_em_foo }) }
70
- it 'should include tracked attributes only' do
71
- expect(subject['emb_one'][0]).to be_nil
72
-
73
- expect(subject['emb_one'][1].keys.size).to eq 2
74
- expect(subject['emb_one'][1]['_id']).to eq emb_one._id
75
- expect(subject['emb_one'][1]['f_em_foo']).to eq 'Foo'
76
- end
77
- end
78
-
79
- context 'with untracked embeds_one object' do
80
- before(:each) { ModelOne.track_history(on: :fields) }
81
- it 'should not include embeds_one attributes' do
82
- expect(subject['emb_one']).to be_nil
83
- end
84
- end
85
-
86
- context 'with tracked embeds_many objects' do
87
- before(:each) { ModelOne.track_history(on: { emb_threes: :f_em_foo }) }
88
- it 'should include tracked attributes only' do
89
- expect(subject['emb_threes'][0]).to be_nil
90
-
91
- expect(subject['emb_threes'][1][0].keys.count).to eq 2
92
- expect(subject['emb_threes'][1][0]['_id']).to eq emb_threes.first._id
93
- expect(subject['emb_threes'][1][0]['f_em_foo']).to eq 'Foo'
94
- end
95
- end
96
-
97
- context 'with untracked embeds_many objects' do
98
- before(:each) { ModelOne.track_history(on: :fields) }
99
- it 'should include not tracked embeds_many attributes' do
100
- expect(subject['emb_threes']).to be_nil
101
- end
102
- end
103
-
104
- context 'when embeds_one object blank' do
105
- let(:emb_one) { nil }
106
-
107
- it 'should not include embeds_one model key' do
108
- expect(subject.keys).to_not include 'emb_one'
109
- end
110
- end
111
-
112
- describe 'embeds_one' do
113
- before(:all) do
114
- Mail = Class.new do
115
- include Mongoid::Document
116
- include Mongoid::History::Trackable
117
- store_in collection: :mails
118
- field :provider
119
- embeds_one :mail_subject, inverse_class_name: 'MailSubject'
120
- end
121
-
122
- MailSubject = Class.new do
123
- include Mongoid::Document
124
- field :content
125
- embedded_in :mail
126
- end
127
- end
128
-
129
- before(:each) do
130
- Mail.instance_variable_set(:@history_trackable_options, nil)
131
- Mail.track_history(on: :mail_subject)
132
- end
133
-
134
- let(:mail) { Mail.new(mail_subject: mail_subject) }
135
- let(:mail_subject) { nil }
136
- subject { mail.send(:modified_attributes_for_create)['mail_subject'] }
137
-
138
- context 'when obj not built' do
139
- it { is_expected.to be_nil }
140
- end
141
-
142
- context 'when obj does not respond to paranoia_field' do
143
- let(:mail_subject) { MailSubject.new(content: 'Content') }
144
- it { is_expected.to eq [nil, { '_id' => mail_subject._id, 'content' => 'Content' }] }
145
- end
146
-
147
- context 'when obj not soft-deleted' do
148
- before(:each) { allow(mail_subject).to receive(:deleted_at) { nil } }
149
- let(:mail_subject) { MailSubject.new(content: 'Content') }
150
- it { is_expected.to eq [nil, { '_id' => mail_subject._id, 'content' => 'Content' }] }
151
- end
152
-
153
- context 'when obj soft-deleted' do
154
- before(:each) { allow(mail_subject).to receive(:deleted_at) { Time.now } }
155
- let(:mail_subject) { MailSubject.new(content: 'Content') }
156
- it { is_expected.to be_nil }
157
- end
158
-
159
- after(:all) do
160
- Object.send(:remove_const, :MailSubject)
161
- Object.send(:remove_const, :Mail)
162
- end
163
- end
164
-
165
- describe 'paranoia' do
166
- before(:all) do
167
- ModelParanoia = Class.new do
168
- include Mongoid::Document
169
- include Mongoid::History::Trackable
170
- store_in collection: :model_paranoias
171
- embeds_many :emb_para_ones, inverse_class_name: 'EmbParaOne'
172
- end
173
-
174
- EmbParaOne = Class.new do
175
- include Mongoid::Document
176
- field :em_foo
177
- field :deleted_at
178
- embedded_in :model_paranoia
179
- end
180
- end
181
-
182
- let(:emb_para_one) { EmbParaOne.new(em_foo: 'Em-Foo') }
183
- let(:model_paranoia) { ModelParanoia.new(emb_para_ones: [emb_para_one]) }
184
-
185
- context 'when does not respond to paranoia_field' do
186
- before(:each) do
187
- ModelParanoia.instance_variable_set(:@history_trackable_options, nil)
188
- ModelParanoia.track_history(on: :emb_para_ones)
189
- end
190
-
191
- subject { model_paranoia.send(:modified_attributes_for_create) }
192
-
193
- it 'should include tracked embeds_many objects attributes' do
194
- expect(subject['emb_para_ones'][0]).to be_nil
195
- expect(subject['emb_para_ones'][1].size).to eq 1
196
- expect(subject['emb_para_ones'][1][0]['_id']).to be_a bson_class
197
- expect(subject['emb_para_ones'][1][0]['em_foo']).to eq 'Em-Foo'
198
- end
199
- end
200
-
201
- context 'when responds to paranoia_field' do
202
- before(:each) do
203
- ModelParanoia.instance_variable_set(:@history_trackable_options, nil)
204
- ModelParanoia.track_history(on: :emb_para_ones)
205
- allow(emb_para_one).to receive(:deleted_at) { Time.now }
206
- allow(emb_para_one_2).to receive(:deleted_at) { nil }
207
- end
208
-
209
- let(:model_paranoia) { ModelParanoia.new(emb_para_ones: [emb_para_one, emb_para_one_2]) }
210
- let(:emb_para_one) { EmbParaOne.new(em_foo: 'Em-Foo') }
211
- let(:emb_para_one_2) { EmbParaOne.new(em_foo: 'Em-Foo-2') }
212
-
213
- subject { model_paranoia.send(:modified_attributes_for_create) }
214
-
215
- it 'should not include deleted objects attributes' do
216
- expect(subject['emb_para_ones'][0]).to be_nil
217
- expect(subject['emb_para_ones'][1]).to eq [{ '_id' => emb_para_one_2._id, 'em_foo' => 'Em-Foo-2' }]
218
- end
219
- end
220
-
221
- after(:all) do
222
- Object.send(:remove_const, :ModelParanoia)
223
- Object.send(:remove_const, :EmbParaOne)
224
- end
225
- end
226
- end
227
-
228
- describe '#modified_attributes_for_update' do
229
- before(:each) do
230
- model_one.save!
231
- ModelOne.clear_trackable_memoization
232
- allow(ModelOne).to receive(:dynamic_enabled?) { false }
233
- allow(model_one).to receive(:changes) { changes }
234
- end
235
- let(:changes) { {} }
236
- subject { model_one.send(:modified_attributes_for_update) }
237
-
238
- context 'when embeds_one attributes passed in options' do
239
- before(:each) { ModelOne.track_history(on: { emb_one: :f_em_foo }) }
240
- let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }, { 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }] } }
241
- it { expect(subject['emb_one'][0]).to eq('f_em_foo' => 'Foo') }
242
- it { expect(subject['emb_one'][1]).to eq('f_em_foo' => 'Foo-new') }
243
- end
244
-
245
- context 'when embeds_one relation passed in options' do
246
- before(:each) { ModelOne.track_history(on: :emb_one) }
247
- let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }, { 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }] } }
248
- it { expect(subject['emb_one'][0]).to eq('f_em_foo' => 'Foo', 'fmb' => 'Bar') }
249
- it { expect(subject['emb_one'][1]).to eq('f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new') }
250
- end
251
-
252
- context 'when embeds_one relation not tracked' do
253
- before(:each) { ModelOne.track_history(on: :fields) }
254
- let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo' }, { 'f_em_foo' => 'Foo-new' }] } }
255
- it { expect(subject['emb_one']).to be_nil }
256
- end
257
-
258
- context 'when embeds_many attributes passed in options' do
259
- before(:each) { ModelOne.track_history(on: { emb_threes: :f_em_foo }) }
260
- let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] } }
261
- it { expect(subject['emb_threes']).to eq [[{ 'f_em_foo' => 'Foo' }], [{ 'f_em_foo' => 'Foo-new' }]] }
262
- end
263
-
264
- context 'when embeds_many relation passed in options' do
265
- before(:each) { ModelOne.track_history(on: :emb_threes) }
266
- let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] } }
267
- it { expect(subject['emb_threes']).to eq [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] }
268
- end
269
-
270
- context 'when embeds_many relation not tracked' do
271
- before(:each) { ModelOne.track_history(on: :fields) }
272
- let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo' }], [{ 'f_em_foo' => 'Foo-new' }]] } }
273
- it { expect(subject['emb_threes']).to be_nil }
274
- end
275
-
276
- context 'when field tracked' do
277
- before(:each) { ModelOne.track_history(on: :foo) }
278
- let(:changes) { { 'foo' => ['Foo', 'Foo-new'], 'b' => ['Bar', 'Bar-new'] } }
279
- it { is_expected.to eq('foo' => ['Foo', 'Foo-new']) }
280
- end
281
-
282
- context 'when field not tracked' do
283
- before(:each) { ModelOne.track_history(on: []) }
284
- let(:changes) { { 'foo' => ['Foo', 'Foo-new'] } }
285
- it { is_expected.to eq({}) }
286
- end
287
-
288
- describe 'embeds_one' do
289
- before(:all) do
290
- Email = Class.new do
291
- include Mongoid::Document
292
- include Mongoid::History::Trackable
293
- store_in collection: :emails
294
- field :provider
295
- embeds_one :email_subject, inverse_class_name: 'EmailSubject'
296
- end
297
-
298
- EmailSubject = Class.new do
299
- include Mongoid::Document
300
- include Mongoid::History::Trackable
301
- field :content
302
- embedded_in :email_subject
303
- end
304
- end
305
-
306
- before(:each) do
307
- Email.instance_variable_set(:@history_trackable_options, nil)
308
- Email.track_history(on: :email_subject)
309
- allow(Email).to receive(:dynamic_enabled?) { false }
310
- allow(email).to receive(:changes) { changes }
311
- end
312
-
313
- let(:email) { Email.new }
314
- let(:changes) { {} }
315
- subject { email.send(:modified_attributes_for_update)['email_subject'] }
316
-
317
- context 'when paranoia_field not present' do
318
- let(:changes) { { 'email_subject' => [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] } }
319
- it { is_expected.to eq [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] }
320
- end
321
-
322
- context 'when older soft-deleted' do
323
- let(:changes) { { 'email_subject' => [{ 'content' => 'Content', 'deleted_at' => Time.now }, { 'content' => 'Content-new' }] } }
324
- it { is_expected.to eq [{}, { 'content' => 'Content-new' }] }
325
- end
326
-
327
- context 'when new soft-deleted' do
328
- let(:changes) { { 'email_subject' => [{ 'content' => 'Content' }, { 'content' => 'Content-new', 'deleted_at' => Time.now }] } }
329
- it { is_expected.to eq [{ 'content' => 'Content' }, {}] }
330
- end
331
-
332
- context 'when not soft-deleted' do
333
- let(:changes) do
334
- { 'email_subject' => [{ 'content' => 'Content', 'deleted_at' => nil }, { 'content' => 'Content-new', 'deleted_at' => nil }] }
335
- end
336
- it { is_expected.to eq [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] }
337
- end
338
-
339
- after(:all) do
340
- Object.send(:remove_const, :EmailSubject)
341
- Object.send(:remove_const, :Email)
342
- end
343
- end
344
-
345
- describe 'paranoia_field' do
346
- context 'when embeds_one has alias' do
347
- before(:all) do
348
- # Here i need class name constant in trackable.rb. So, not using `let` to define classes
349
- ModelTwo = Class.new do
350
- include Mongoid::Document
351
- include Mongoid::History::Trackable
352
- store_in collection: :model_twos
353
- embeds_one :emb_two_one, inverse_class_name: 'EmbTwoOne'
354
- end
355
-
356
- EmbTwoOne = Class.new do
357
- include Mongoid::Document
358
- include Mongoid::History::Trackable
359
- field :foo
360
- field :cncl, as: :cancelled_at
361
- embedded_in :model_two
362
- end
363
- end
364
-
365
- before(:each) do
366
- ModelTwo.instance_variable_set(:@history_trackable_options, nil)
367
- ModelTwo.instance_variable_set(:@trackable_settings, nil)
368
- EmbTwoOne.instance_variable_set(:@trackable_settings, nil)
369
-
370
- ModelTwo.track_history on: :emb_two_one
371
- EmbTwoOne.history_settings paranoia_field: :cancelled_at
372
-
373
- allow(ModelTwo).to receive(:dynamic_enabled?) { false }
374
- allow(model_two_obj).to receive(:changes) { changes }
375
- end
376
-
377
- let(:model_two_obj) { ModelTwo.new }
378
- let(:changes) { { 'emb_two_one' => [{ 'foo' => 'Foo', 'cncl' => Time.now }, { 'foo' => 'Foo-new' }] } }
379
- subject { model_two_obj.send(:modified_attributes_for_update)['emb_two_one'] }
380
- it { is_expected.to eq [{}, { 'foo' => 'Foo-new' }] }
381
-
382
- after(:all) do
383
- Object.send(:remove_const, :ModelTwo)
384
- Object.send(:remove_const, :EmbTwoOne)
385
- end
386
- end
387
-
388
- context 'when embeds_many has alias' do
389
- before(:all) do
390
- # Here i need class name constant in trackable.rb. So, not using `let` to define classes
391
- ModelTwo = Class.new do
392
- include Mongoid::Document
393
- include Mongoid::History::Trackable
394
- store_in collection: :model_twos
395
- embeds_many :emb_two_ones, inverse_class_name: 'EmbTwoOne'
396
- end
397
-
398
- EmbTwoOne = Class.new do
399
- include Mongoid::Document
400
- include Mongoid::History::Trackable
401
- field :foo
402
- field :cncl, as: :cancelled_at
403
- embedded_in :model_two
404
- end
405
- end
406
-
407
- before(:each) do
408
- ModelTwo.instance_variable_set(:@history_trackable_options, nil)
409
- ModelTwo.instance_variable_set(:@trackable_settings, nil)
410
- EmbTwoOne.instance_variable_set(:@trackable_settings, nil)
411
-
412
- ModelTwo.track_history on: :emb_two_ones
413
- EmbTwoOne.history_settings paranoia_field: :cancelled_at
414
-
415
- allow(ModelTwo).to receive(:dynamic_enabled?) { false }
416
- allow(model_two_obj).to receive(:changes) { changes }
417
- end
418
-
419
- let(:model_two_obj) { ModelTwo.new }
420
- let(:changes) { { 'emb_two_ones' => [[{ 'foo' => 'Foo', 'cncl' => Time.now }], [{ 'foo' => 'Foo-new' }]] } }
421
- subject { model_two_obj.send(:modified_attributes_for_update)['emb_two_ones'] }
422
- it { is_expected.to eq [[], [{ 'foo' => 'Foo-new' }]] }
423
-
424
- after(:all) do
425
- Object.send(:remove_const, :ModelTwo)
426
- Object.send(:remove_const, :EmbTwoOne)
427
- end
428
- end
429
- end
430
- end
431
-
432
- describe '#modified_attributes_for_destroy' do
433
- before(:each) do
434
- allow(ModelOne).to receive(:dynamic_enabled?) { false }
435
- model_one.save!
436
- ModelOne.clear_trackable_memoization
437
- end
438
- subject { model_one.send(:modified_attributes_for_destroy) }
439
-
440
- context 'with tracked embeds_one object' do
441
- before(:each) { ModelOne.track_history(on: { emb_one: :f_em_foo }) }
442
- it 'should include tracked attributes only' do
443
- expect(subject['emb_one'][0].keys.size).to eq 2
444
- expect(subject['emb_one'][0]['_id']).to eq emb_one._id
445
- expect(subject['emb_one'][0]['f_em_foo']).to eq 'Foo'
446
-
447
- expect(subject['emb_one'][1]).to be_nil
448
- end
449
- end
450
-
451
- context 'with untracked embeds_one object' do
452
- before(:each) { ModelOne.track_history(on: :fields) }
453
- it 'should not include embeds_one attributes' do
454
- expect(subject['emb_one']).to be_nil
455
- end
456
- end
457
-
458
- context 'with tracked embeds_many objects' do
459
- before(:each) { ModelOne.track_history(on: { emb_threes: :f_em_foo }) }
460
- it 'should include tracked attributes only' do
461
- expect(subject['emb_threes'][0][0].keys.count).to eq 2
462
- expect(subject['emb_threes'][0][0]['_id']).to eq emb_threes.first._id
463
- expect(subject['emb_threes'][0][0]['f_em_foo']).to eq 'Foo'
464
-
465
- expect(subject['emb_threes'][1]).to be_nil
466
- end
467
- end
468
-
469
- context 'with untracked embeds_many objects' do
470
- before(:each) { ModelOne.track_history(on: :fields) }
471
- it 'should include not tracked embeds_many attributes' do
472
- expect(subject['emb_threes']).to be_nil
473
- end
474
- end
475
- end
476
-
477
- after :all do
478
- Object.send(:remove_const, :ModelOne)
479
- Object.send(:remove_const, :EmbOne)
480
- Object.send(:remove_const, :EmbTwo)
481
- Object.send(:remove_const, :EmbThree)
482
- Object.send(:remove_const, :EmbFour)
483
- end
484
- end
485
- end
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::History::Trackable do
4
+ describe 'MyInstanceMethods' do
5
+ before :each do
6
+ class ModelOne
7
+ include Mongoid::Document
8
+ include Mongoid::History::Trackable
9
+
10
+ store_in collection: :model_ones
11
+
12
+ field :foo
13
+ field :b, as: :bar
14
+
15
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
16
+ embeds_one :emb_one
17
+ embeds_one :emb_two, store_as: :emt
18
+ embeds_many :emb_threes
19
+ embeds_many :emb_fours, store_as: :emfs
20
+ else
21
+ embeds_one :emb_one, inverse_class_name: 'EmbOne'
22
+ embeds_one :emb_two, store_as: :emt, inverse_class_name: 'EmbTwo'
23
+ embeds_many :emb_threes, inverse_class_name: 'EmbThree'
24
+ embeds_many :emb_fours, store_as: :emfs, inverse_class_name: 'EmbFour'
25
+ end
26
+ end
27
+
28
+ class EmbOne
29
+ include Mongoid::Document
30
+ include Mongoid::History::Trackable
31
+
32
+ field :f_em_foo
33
+ field :fmb, as: :f_em_bar
34
+
35
+ embedded_in :model_one
36
+ end
37
+
38
+ class EmbTwo
39
+ include Mongoid::Document
40
+ include Mongoid::History::Trackable
41
+
42
+ field :baz
43
+ embedded_in :model_one
44
+ end
45
+
46
+ class EmbThree
47
+ include Mongoid::Document
48
+ include Mongoid::History::Trackable
49
+
50
+ field :f_em_foo
51
+ field :fmb, as: :f_em_bar
52
+
53
+ embedded_in :model_one
54
+ end
55
+
56
+ class EmbFour
57
+ include Mongoid::Document
58
+ include Mongoid::History::Trackable
59
+
60
+ field :baz
61
+ embedded_in :model_one
62
+ end
63
+ end
64
+
65
+ after :each do
66
+ Object.send(:remove_const, :ModelOne)
67
+ Object.send(:remove_const, :EmbOne)
68
+ Object.send(:remove_const, :EmbTwo)
69
+ Object.send(:remove_const, :EmbThree)
70
+ Object.send(:remove_const, :EmbFour)
71
+ end
72
+
73
+ let(:bson_class) { defined?(BSON::ObjectId) ? BSON::ObjectId : Moped::BSON::ObjectId }
74
+
75
+ let(:emb_one) { EmbOne.new(f_em_foo: 'Foo', f_em_bar: 'Bar') }
76
+ let(:emb_threes) { [EmbThree.new(f_em_foo: 'Foo', f_em_bar: 'Bar')] }
77
+ let(:model_one) { ModelOne.new(foo: 'Foo', bar: 'Bar', emb_one: emb_one, emb_threes: emb_threes) }
78
+
79
+ describe '#modified_attributes_for_create' do
80
+ before :each do
81
+ ModelOne.track_history modifier_field_optional: true, on: %i[foo emb_one emb_threes]
82
+ end
83
+
84
+ subject { model_one.send(:modified_attributes_for_create) }
85
+
86
+ context 'with tracked embeds_one object' do
87
+ before :each do
88
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_one: :f_em_foo })
89
+ end
90
+ it 'should include tracked attributes only' do
91
+ expect(subject['emb_one'][0]).to be_nil
92
+
93
+ expect(subject['emb_one'][1].keys.size).to eq 2
94
+ expect(subject['emb_one'][1]['_id']).to eq emb_one._id
95
+ expect(subject['emb_one'][1]['f_em_foo']).to eq 'Foo'
96
+ end
97
+ end
98
+
99
+ context 'with untracked embeds_one object' do
100
+ before :each do
101
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
102
+ end
103
+ it 'should not include embeds_one attributes' do
104
+ expect(subject['emb_one']).to be_nil
105
+ end
106
+ end
107
+
108
+ context 'with tracked embeds_many objects' do
109
+ before :each do
110
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_threes: :f_em_foo })
111
+ end
112
+ it 'should include tracked attributes only' do
113
+ expect(subject['emb_threes'][0]).to be_nil
114
+
115
+ expect(subject['emb_threes'][1][0].keys.count).to eq 2
116
+ expect(subject['emb_threes'][1][0]['_id']).to eq emb_threes.first._id
117
+ expect(subject['emb_threes'][1][0]['f_em_foo']).to eq 'Foo'
118
+ end
119
+ end
120
+
121
+ context 'with untracked embeds_many objects' do
122
+ before :each do
123
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
124
+ end
125
+ it 'should include not tracked embeds_many attributes' do
126
+ expect(subject['emb_threes']).to be_nil
127
+ end
128
+ end
129
+
130
+ context 'when embeds_one object blank' do
131
+ let(:emb_one) { nil }
132
+
133
+ it 'should not include embeds_one model key' do
134
+ expect(subject.keys).to_not include 'emb_one'
135
+ end
136
+ end
137
+
138
+ describe 'embeds_one' do
139
+ before :each do
140
+ class Mail
141
+ include Mongoid::Document
142
+ include Mongoid::History::Trackable
143
+
144
+ store_in collection: :mails
145
+ field :provider
146
+
147
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
148
+ embeds_one :mail_subject
149
+ else
150
+ embeds_one :mail_subject, inverse_class_name: 'MailSubject'
151
+ end
152
+
153
+ track_history on: :mail_subject
154
+ end
155
+
156
+ class MailSubject
157
+ include Mongoid::Document
158
+
159
+ field :content
160
+ embedded_in :mail
161
+ end
162
+ end
163
+
164
+ after :each do
165
+ Object.send(:remove_const, :MailSubject)
166
+ Object.send(:remove_const, :Mail)
167
+ end
168
+
169
+ let(:mail) { Mail.new(mail_subject: mail_subject) }
170
+ let(:mail_subject) { nil }
171
+ subject { mail.send(:modified_attributes_for_create)['mail_subject'] }
172
+
173
+ context 'when obj not built' do
174
+ it { is_expected.to be_nil }
175
+ end
176
+
177
+ context 'when obj does not respond to paranoia_field' do
178
+ let(:mail_subject) { MailSubject.new(content: 'Content') }
179
+ it { is_expected.to eq [nil, { '_id' => mail_subject._id, 'content' => 'Content' }] }
180
+ end
181
+
182
+ context 'when obj not soft-deleted' do
183
+ before :each do
184
+ allow(mail_subject).to receive(:deleted_at) { nil }
185
+ end
186
+ let(:mail_subject) { MailSubject.new(content: 'Content') }
187
+ it { is_expected.to eq [nil, { '_id' => mail_subject._id, 'content' => 'Content' }] }
188
+ end
189
+
190
+ context 'when obj soft-deleted' do
191
+ before :each do
192
+ allow(mail_subject).to receive(:deleted_at) { Time.now }
193
+ end
194
+ let(:mail_subject) { MailSubject.new(content: 'Content') }
195
+ it { is_expected.to be_nil }
196
+ end
197
+ end
198
+
199
+ describe 'paranoia' do
200
+ before :each do
201
+ class ModelParanoia
202
+ include Mongoid::Document
203
+ include Mongoid::History::Trackable
204
+
205
+ store_in collection: :model_paranoias
206
+
207
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
208
+ embeds_many :emb_para_ones
209
+ else
210
+ embeds_many :emb_para_ones, inverse_class_name: 'EmbParaOne'
211
+ end
212
+ end
213
+
214
+ class EmbParaOne
215
+ include Mongoid::Document
216
+
217
+ field :em_foo
218
+ field :deleted_at
219
+
220
+ embedded_in :model_paranoia
221
+ end
222
+ end
223
+
224
+ after :each do
225
+ Object.send(:remove_const, :ModelParanoia)
226
+ Object.send(:remove_const, :EmbParaOne)
227
+ end
228
+
229
+ let(:emb_para_one) { EmbParaOne.new(em_foo: 'Em-Foo') }
230
+ let(:model_paranoia) { ModelParanoia.new(emb_para_ones: [emb_para_one]) }
231
+
232
+ context 'when does not respond to paranoia_field' do
233
+ before :each do
234
+ ModelParanoia.track_history(on: :emb_para_ones)
235
+ end
236
+
237
+ subject { model_paranoia.send(:modified_attributes_for_create) }
238
+
239
+ it 'should include tracked embeds_many objects attributes' do
240
+ expect(subject['emb_para_ones'][0]).to be_nil
241
+ expect(subject['emb_para_ones'][1].size).to eq 1
242
+ expect(subject['emb_para_ones'][1][0]['_id']).to be_a bson_class
243
+ expect(subject['emb_para_ones'][1][0]['em_foo']).to eq 'Em-Foo'
244
+ end
245
+ end
246
+
247
+ context 'when responds to paranoia_field' do
248
+ before :each do
249
+ ModelParanoia.track_history(on: :emb_para_ones)
250
+ allow(emb_para_one).to receive(:deleted_at) { Time.now }
251
+ allow(emb_para_one_2).to receive(:deleted_at) { nil }
252
+ end
253
+
254
+ let(:model_paranoia) { ModelParanoia.new(emb_para_ones: [emb_para_one, emb_para_one_2]) }
255
+ let(:emb_para_one) { EmbParaOne.new(em_foo: 'Em-Foo') }
256
+ let(:emb_para_one_2) { EmbParaOne.new(em_foo: 'Em-Foo-2') }
257
+
258
+ subject { model_paranoia.send(:modified_attributes_for_create) }
259
+
260
+ it 'should not include deleted objects attributes' do
261
+ expect(subject['emb_para_ones'][0]).to be_nil
262
+ expect(subject['emb_para_ones'][1]).to eq [{ '_id' => emb_para_one_2._id, 'em_foo' => 'Em-Foo-2' }]
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ describe '#modified_attributes_for_update' do
269
+ before :each do
270
+ model_one.save!
271
+ allow(ModelOne).to receive(:dynamic_enabled?) { false }
272
+ allow(model_one).to receive(:changes) { changes }
273
+ end
274
+ let(:changes) { {} }
275
+ subject { model_one.send(:modified_attributes_for_update) }
276
+
277
+ context 'when embeds_one attributes passed in options' do
278
+ before :each do
279
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_one: :f_em_foo })
280
+ end
281
+ let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }, { 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }] } }
282
+ it { expect(subject['emb_one'][0]).to eq('f_em_foo' => 'Foo') }
283
+ it { expect(subject['emb_one'][1]).to eq('f_em_foo' => 'Foo-new') }
284
+ end
285
+
286
+ context 'when embeds_one relation passed in options' do
287
+ before :each do
288
+ ModelOne.track_history(modifier_field_optional: true, on: :emb_one)
289
+ end
290
+ let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }, { 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }] } }
291
+ it { expect(subject['emb_one'][0]).to eq('f_em_foo' => 'Foo', 'fmb' => 'Bar') }
292
+ it { expect(subject['emb_one'][1]).to eq('f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new') }
293
+ end
294
+
295
+ context 'when embeds_one relation not tracked' do
296
+ before :each do
297
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
298
+ end
299
+ let(:changes) { { 'emb_one' => [{ 'f_em_foo' => 'Foo' }, { 'f_em_foo' => 'Foo-new' }] } }
300
+ it { expect(subject['emb_one']).to be_nil }
301
+ end
302
+
303
+ context 'when embeds_many attributes passed in options' do
304
+ before :each do
305
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_threes: :f_em_foo })
306
+ end
307
+ let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] } }
308
+ it { expect(subject['emb_threes']).to eq [[{ 'f_em_foo' => 'Foo' }], [{ 'f_em_foo' => 'Foo-new' }]] }
309
+ end
310
+
311
+ context 'when embeds_many relation passed in options' do
312
+ before :each do
313
+ ModelOne.track_history(modifier_field_optional: true, on: :emb_threes)
314
+ end
315
+ let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] } }
316
+ it { expect(subject['emb_threes']).to eq [[{ 'f_em_foo' => 'Foo', 'fmb' => 'Bar' }], [{ 'f_em_foo' => 'Foo-new', 'fmb' => 'Bar-new' }]] }
317
+ end
318
+
319
+ context 'when embeds_many relation not tracked' do
320
+ before :each do
321
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
322
+ end
323
+ let(:changes) { { 'emb_threes' => [[{ 'f_em_foo' => 'Foo' }], [{ 'f_em_foo' => 'Foo-new' }]] } }
324
+ it { expect(subject['emb_threes']).to be_nil }
325
+ end
326
+
327
+ context 'when field tracked' do
328
+ before :each do
329
+ ModelOne.track_history(modifier_field_optional: true, on: :foo)
330
+ end
331
+ let(:changes) { { 'foo' => ['Foo', 'Foo-new'], 'b' => ['Bar', 'Bar-new'] } }
332
+ it { is_expected.to eq('foo' => ['Foo', 'Foo-new']) }
333
+ end
334
+
335
+ context 'when field not tracked' do
336
+ before :each do
337
+ ModelOne.track_history(modifier_field_optional: true, on: [])
338
+ end
339
+ let(:changes) { { 'foo' => ['Foo', 'Foo-new'] } }
340
+ it { is_expected.to eq({}) }
341
+ end
342
+
343
+ describe 'embeds_one' do
344
+ before :each do
345
+ class Email
346
+ include Mongoid::Document
347
+ include Mongoid::History::Trackable
348
+
349
+ store_in collection: :emails
350
+ field :provider
351
+
352
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
353
+ embeds_one :email_subject
354
+ else
355
+ embeds_one :email_subject, inverse_class_name: 'EmailSubject'
356
+ end
357
+
358
+ track_history on: :email_subject
359
+ end
360
+
361
+ class EmailSubject
362
+ include Mongoid::Document
363
+ include Mongoid::History::Trackable
364
+
365
+ field :content
366
+ embedded_in :email_subject
367
+ end
368
+ end
369
+
370
+ after :each do
371
+ Object.send(:remove_const, :EmailSubject)
372
+ Object.send(:remove_const, :Email)
373
+ end
374
+
375
+ before :each do
376
+ allow(Email).to receive(:dynamic_enabled?) { false }
377
+ allow(email).to receive(:changes) { changes }
378
+ end
379
+
380
+ let(:email) { Email.new }
381
+ let(:changes) { {} }
382
+ subject { email.send(:modified_attributes_for_update)['email_subject'] }
383
+
384
+ context 'when paranoia_field not present' do
385
+ let(:changes) { { 'email_subject' => [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] } }
386
+ it { is_expected.to eq [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] }
387
+ end
388
+
389
+ context 'when older soft-deleted' do
390
+ let(:changes) { { 'email_subject' => [{ 'content' => 'Content', 'deleted_at' => Time.now }, { 'content' => 'Content-new' }] } }
391
+ it { is_expected.to eq [{}, { 'content' => 'Content-new' }] }
392
+ end
393
+
394
+ context 'when new soft-deleted' do
395
+ let(:changes) { { 'email_subject' => [{ 'content' => 'Content' }, { 'content' => 'Content-new', 'deleted_at' => Time.now }] } }
396
+ it { is_expected.to eq [{ 'content' => 'Content' }, {}] }
397
+ end
398
+
399
+ context 'when not soft-deleted' do
400
+ let(:changes) do
401
+ { 'email_subject' => [{ 'content' => 'Content', 'deleted_at' => nil }, { 'content' => 'Content-new', 'deleted_at' => nil }] }
402
+ end
403
+ it { is_expected.to eq [{ 'content' => 'Content' }, { 'content' => 'Content-new' }] }
404
+ end
405
+ end
406
+
407
+ describe 'paranoia_field' do
408
+ context 'when embeds_one has alias' do
409
+ before :each do
410
+ class ModelTwo
411
+ include Mongoid::Document
412
+ include Mongoid::History::Trackable
413
+
414
+ store_in collection: :model_twos
415
+
416
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
417
+ embeds_one :emb_two_one
418
+ else
419
+ embeds_one :emb_two_one, inverse_class_name: 'EmbTwoOne'
420
+ end
421
+
422
+ track_history on: :emb_two_one
423
+ end
424
+
425
+ class EmbTwoOne
426
+ include Mongoid::Document
427
+ include Mongoid::History::Trackable
428
+
429
+ field :foo
430
+ field :cncl, as: :cancelled_at
431
+
432
+ embedded_in :model_two
433
+
434
+ history_settings paranoia_field: :cancelled_at
435
+ end
436
+ end
437
+
438
+ after :each do
439
+ Object.send(:remove_const, :ModelTwo)
440
+ Object.send(:remove_const, :EmbTwoOne)
441
+ end
442
+
443
+ before :each do
444
+ allow(ModelTwo).to receive(:dynamic_enabled?) { false }
445
+ allow(model_two_obj).to receive(:changes) { changes }
446
+ end
447
+
448
+ let(:model_two_obj) { ModelTwo.new }
449
+ let(:changes) { { 'emb_two_one' => [{ 'foo' => 'Foo', 'cncl' => Time.now }, { 'foo' => 'Foo-new' }] } }
450
+
451
+ subject { model_two_obj.send(:modified_attributes_for_update)['emb_two_one'] }
452
+ it { is_expected.to eq [{}, { 'foo' => 'Foo-new' }] }
453
+ end
454
+
455
+ context 'when embeds_many has alias' do
456
+ before :each do
457
+ class ModelTwo
458
+ include Mongoid::Document
459
+ include Mongoid::History::Trackable
460
+
461
+ store_in collection: :model_twos
462
+
463
+ if Mongoid::Compatibility::Version.mongoid7_or_newer?
464
+ embeds_many :emb_two_ones
465
+ else
466
+ embeds_many :emb_two_ones, inverse_class_name: 'EmbTwoOne'
467
+ end
468
+
469
+ track_history on: :emb_two_ones
470
+ end
471
+
472
+ class EmbTwoOne
473
+ include Mongoid::Document
474
+ include Mongoid::History::Trackable
475
+
476
+ field :foo
477
+ field :cncl, as: :cancelled_at
478
+
479
+ embedded_in :model_two
480
+
481
+ history_settings paranoia_field: :cancelled_at
482
+ end
483
+ end
484
+
485
+ after :each do
486
+ Object.send(:remove_const, :ModelTwo)
487
+ Object.send(:remove_const, :EmbTwoOne)
488
+ end
489
+
490
+ before :each do
491
+ allow(ModelTwo).to receive(:dynamic_enabled?) { false }
492
+ allow(model_two_obj).to receive(:changes) { changes }
493
+ end
494
+
495
+ let(:model_two_obj) { ModelTwo.new }
496
+ let(:changes) { { 'emb_two_ones' => [[{ 'foo' => 'Foo', 'cncl' => Time.now }], [{ 'foo' => 'Foo-new' }]] } }
497
+ subject { model_two_obj.send(:modified_attributes_for_update)['emb_two_ones'] }
498
+ it { is_expected.to eq [[], [{ 'foo' => 'Foo-new' }]] }
499
+ end
500
+ end
501
+ end
502
+
503
+ describe '#modified_attributes_for_destroy' do
504
+ before :each do
505
+ allow(ModelOne).to receive(:dynamic_enabled?) { false }
506
+ model_one.save!
507
+ end
508
+ subject { model_one.send(:modified_attributes_for_destroy) }
509
+
510
+ context 'with tracked embeds_one object' do
511
+ before :each do
512
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_one: :f_em_foo })
513
+ end
514
+ it 'should include tracked attributes only' do
515
+ expect(subject['emb_one'][0].keys.size).to eq 2
516
+ expect(subject['emb_one'][0]['_id']).to eq emb_one._id
517
+ expect(subject['emb_one'][0]['f_em_foo']).to eq 'Foo'
518
+
519
+ expect(subject['emb_one'][1]).to be_nil
520
+ end
521
+ end
522
+
523
+ context 'with untracked embeds_one object' do
524
+ before :each do
525
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
526
+ end
527
+ it 'should not include embeds_one attributes' do
528
+ expect(subject['emb_one']).to be_nil
529
+ end
530
+ end
531
+
532
+ context 'with tracked embeds_many objects' do
533
+ before :each do
534
+ ModelOne.track_history(modifier_field_optional: true, on: { emb_threes: :f_em_foo })
535
+ end
536
+ it 'should include tracked attributes only' do
537
+ expect(subject['emb_threes'][0][0].keys.count).to eq 2
538
+ expect(subject['emb_threes'][0][0]['_id']).to eq emb_threes.first._id
539
+ expect(subject['emb_threes'][0][0]['f_em_foo']).to eq 'Foo'
540
+
541
+ expect(subject['emb_threes'][1]).to be_nil
542
+ end
543
+ end
544
+
545
+ context 'with untracked embeds_many objects' do
546
+ before :each do
547
+ ModelOne.track_history(modifier_field_optional: true, on: :fields)
548
+ end
549
+ it 'should include not tracked embeds_many attributes' do
550
+ expect(subject['emb_threes']).to be_nil
551
+ end
552
+ end
553
+ end
554
+ end
555
+ end