mongoid-history 0.5.0 → 0.6.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +16 -3
  3. data/.travis.yml +23 -8
  4. data/CHANGELOG.md +20 -6
  5. data/Dangerfile +1 -0
  6. data/Gemfile +22 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +130 -18
  9. data/lib/mongoid/history.rb +32 -15
  10. data/lib/mongoid/history/attributes/base.rb +31 -0
  11. data/lib/mongoid/history/attributes/create.rb +52 -0
  12. data/lib/mongoid/history/attributes/destroy.rb +36 -0
  13. data/lib/mongoid/history/attributes/update.rb +43 -0
  14. data/lib/mongoid/history/options.rb +153 -0
  15. data/lib/mongoid/history/trackable.rb +170 -72
  16. data/lib/mongoid/history/tracker.rb +39 -23
  17. data/lib/mongoid/history/version.rb +1 -1
  18. data/mongoid-history.gemspec +0 -8
  19. data/spec/integration/embedded_in_polymorphic_spec.rb +8 -0
  20. data/spec/integration/integration_spec.rb +4 -0
  21. data/spec/integration/nested_embedded_documents_spec.rb +13 -6
  22. data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +24 -24
  23. data/spec/spec_helper.rb +6 -0
  24. data/spec/unit/attributes/base_spec.rb +54 -0
  25. data/spec/unit/attributes/create_spec.rb +315 -0
  26. data/spec/unit/attributes/destroy_spec.rb +218 -0
  27. data/spec/unit/attributes/update_spec.rb +210 -0
  28. data/spec/unit/embedded_methods_spec.rb +69 -0
  29. data/spec/unit/history_spec.rb +35 -0
  30. data/spec/unit/my_instance_methods_spec.rb +485 -0
  31. data/spec/unit/options_spec.rb +294 -0
  32. data/spec/unit/singleton_methods_spec.rb +338 -0
  33. data/spec/unit/store/default_store_spec.rb +11 -0
  34. data/spec/unit/store/request_store_spec.rb +13 -0
  35. data/spec/unit/trackable_spec.rb +335 -68
  36. data/spec/unit/tracker_spec.rb +153 -0
  37. metadata +31 -102
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Default Store' do
4
+ describe 'Mongoid::History' do
5
+ describe '.store' do
6
+ it 'should return Thread object' do
7
+ expect(Mongoid::History.store).to be_a Thread
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'RequestStore' do
4
+ before { stub_const('RequestStore', RequestStoreTemp) }
5
+
6
+ describe 'Mongoid::History' do
7
+ describe '.store' do
8
+ it 'should return RequestStore' do
9
+ expect(Mongoid::History.store).to be_a Hash
10
+ end
11
+ end
12
+ end
13
+ end
@@ -34,15 +34,19 @@ describe Mongoid::History::Trackable do
34
34
  end
35
35
  before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options }
36
36
  let(:expected_option) do
37
- { on: :all,
37
+ { on: %i(foo),
38
+ except: %w(created_at updated_at),
39
+ tracker_class_name: nil,
38
40
  modifier_field: :modifier,
39
41
  version_field: :version,
40
42
  changes_method: :changes,
41
43
  scope: :my_model,
42
- except: %w(created_at updated_at),
43
44
  track_create: false,
44
45
  track_update: true,
45
- track_destroy: false }
46
+ track_destroy: false,
47
+ fields: %w(foo),
48
+ relations: { embeds_one: {}, embeds_many: {} },
49
+ dynamic: [] }
46
50
  end
47
51
  let(:regular_fields) { ['foo'] }
48
52
  let(:reserved_fields) { %w(_id version modifier_id) }
@@ -163,103 +167,128 @@ describe Mongoid::History::Trackable do
163
167
  end
164
168
 
165
169
  describe '#track_history?' do
166
- context 'when tracking is globally enabled' do
167
- it 'should be enabled on the current thread' do
168
- expect(Mongoid::History.enabled?).to eq(true)
169
- expect(MyModel.new.track_history?).to eq(true)
170
- end
170
+ shared_examples_for 'history tracking' do
171
+ context 'when tracking is globally enabled' do
172
+ it 'should be enabled on the current thread' do
173
+ expect(Mongoid::History.enabled?).to eq(true)
174
+ expect(MyModel.new.track_history?).to eq(true)
175
+ end
176
+
177
+ it 'should be disabled within disable_tracking' do
178
+ MyModel.disable_tracking do
179
+ expect(Mongoid::History.enabled?).to eq(true)
180
+ expect(MyModel.new.track_history?).to eq(false)
181
+ end
182
+ end
171
183
 
172
- it 'should be disabled within disable_tracking' do
173
- MyModel.disable_tracking do
184
+ it 'should be rescued if an exception occurs' do
185
+ begin
186
+ MyModel.disable_tracking do
187
+ fail 'exception'
188
+ end
189
+ rescue
190
+ end
174
191
  expect(Mongoid::History.enabled?).to eq(true)
175
- expect(MyModel.new.track_history?).to eq(false)
192
+ expect(MyModel.new.track_history?).to eq(true)
176
193
  end
177
- end
178
194
 
179
- it 'should be rescued if an exception occurs' do
180
- begin
195
+ it 'should be disabled only for the class that calls disable_tracking' do
196
+ class MyModel2
197
+ include Mongoid::Document
198
+ include Mongoid::History::Trackable
199
+ track_history
200
+ end
201
+
181
202
  MyModel.disable_tracking do
182
- fail 'exception'
203
+ expect(Mongoid::History.enabled?).to eq(true)
204
+ expect(MyModel2.new.track_history?).to eq(true)
183
205
  end
184
- rescue
185
206
  end
186
- expect(Mongoid::History.enabled?).to eq(true)
187
- expect(MyModel.new.track_history?).to eq(true)
188
207
  end
189
208
 
190
- it 'should be disabled only for the class that calls disable_tracking' do
191
- class MyModel2
192
- include Mongoid::Document
193
- include Mongoid::History::Trackable
194
- track_history
209
+ context 'when tracking is globally disabled' do
210
+ it 'should be disabled by the global disablement' do
211
+ Mongoid::History.disable do
212
+ expect(Mongoid::History.enabled?).to eq(false)
213
+ expect(MyModel.new.track_history?).to eq(false)
214
+ end
195
215
  end
196
216
 
197
- MyModel.disable_tracking do
198
- expect(Mongoid::History.enabled?).to eq(true)
199
- expect(MyModel2.new.track_history?).to eq(true)
217
+ it 'should be disabled within disable_tracking' do
218
+ Mongoid::History.disable do
219
+ MyModel.disable_tracking do
220
+ expect(Mongoid::History.enabled?).to eq(false)
221
+ expect(MyModel.new.track_history?).to eq(false)
222
+ end
223
+ end
200
224
  end
201
- end
202
- end
203
225
 
204
- context 'when tracking is globally disabled' do
205
- around(:each) do |example|
206
- Mongoid::History.disable do
207
- example.run
226
+ it 'should be rescued if an exception occurs' do
227
+ Mongoid::History.disable do
228
+ begin
229
+ MyModel.disable_tracking do
230
+ fail 'exception'
231
+ end
232
+ rescue
233
+ end
234
+ expect(Mongoid::History.enabled?).to eq(false)
235
+ expect(MyModel.new.track_history?).to eq(false)
236
+ end
208
237
  end
209
- end
210
238
 
211
- it 'should be disabled by the global disablement' do
212
- expect(Mongoid::History.enabled?).to eq(false)
213
- expect(MyModel.new.track_history?).to eq(false)
214
- end
239
+ it 'should be disabled only for the class that calls disable_tracking' do
240
+ class MyModel2
241
+ include Mongoid::Document
242
+ include Mongoid::History::Trackable
243
+ track_history
244
+ end
215
245
 
216
- it 'should be disabled within disable_tracking' do
217
- MyModel.disable_tracking do
218
- expect(Mongoid::History.enabled?).to eq(false)
219
- expect(MyModel.new.track_history?).to eq(false)
246
+ Mongoid::History.disable do
247
+ MyModel.disable_tracking do
248
+ expect(Mongoid::History.enabled?).to eq(false)
249
+ expect(MyModel2.new.track_history?).to eq(false)
250
+ end
251
+ end
220
252
  end
221
253
  end
222
254
 
223
- it 'should be rescued if an exception occurs' do
255
+ it 'should rescue errors through both local and global tracking scopes' do
224
256
  begin
225
- MyModel.disable_tracking do
226
- fail 'exception'
257
+ Mongoid::History.disable do
258
+ MyModel.disable_tracking do
259
+ fail 'exception'
260
+ end
227
261
  end
228
262
  rescue
229
263
  end
230
- expect(Mongoid::History.enabled?).to eq(false)
231
- expect(MyModel.new.track_history?).to eq(false)
264
+ expect(Mongoid::History.enabled?).to eq(true)
265
+ expect(MyModel.new.track_history?).to eq(true)
232
266
  end
267
+ end
233
268
 
234
- it 'should be disabled only for the class that calls disable_tracking' do
235
- class MyModel2
236
- include Mongoid::Document
237
- include Mongoid::History::Trackable
238
- track_history
239
- end
269
+ context 'when store is Thread' do
270
+ it_behaves_like 'history tracking'
271
+ end
240
272
 
241
- MyModel.disable_tracking do
242
- expect(Mongoid::History.enabled?).to eq(false)
243
- expect(MyModel2.new.track_history?).to eq(false)
244
- end
245
- end
273
+ context 'when store is RequestStore' do
274
+ before { stub_const('RequestStore', RequestStoreTemp) }
275
+ it_behaves_like 'history tracking'
246
276
  end
277
+ end
247
278
 
248
- it 'should rescue errors through both local and global tracking scopes' do
249
- begin
250
- Mongoid::History.disable do
251
- MyModel.disable_tracking do
252
- fail 'exception'
253
- end
279
+ describe ':changes_method' do
280
+ let(:custom_tracker) do
281
+ CustomTracker = Class.new(MyModel) do
282
+ field :key
283
+
284
+ track_history on: :key, changes_method: :my_changes, track_create: true
285
+
286
+ def my_changes
287
+ changes.merge('key' => "Save history-#{key}")
254
288
  end
255
- rescue
256
289
  end
257
- expect(Mongoid::History.enabled?).to eq(true)
258
- expect(MyModel.new.track_history?).to eq(true)
259
290
  end
260
- end
261
291
 
262
- describe ':changes_method' do
263
292
  it 'should default to :changes' do
264
293
  m = MyModel.create
265
294
  expect(m).to receive(:changes).exactly(3).times.and_call_original
@@ -281,6 +310,244 @@ describe Mongoid::History::Trackable do
281
310
  expect(m).to receive(:my_changes).once.and_call_original
282
311
  m.save
283
312
  end
313
+
314
+ it 'should allow an alternate method to be specified on object creation' do
315
+ m = custom_tracker.create(key: 'on object creation')
316
+ history_track = m.history_tracks.last
317
+ expect(history_track.modified['key']).to eq('Save history-on object creation')
318
+ end
319
+ end
320
+ end
321
+
322
+ describe '#history_settings' do
323
+ before(:each) { Mongoid::History.trackable_settings = nil }
324
+
325
+ let(:model_one) do
326
+ Class.new do
327
+ include Mongoid::Document
328
+ include Mongoid::History::Trackable
329
+ store_in collection: :model_ones
330
+ embeds_one :emb_one, inverse_class_name: 'EmbOne'
331
+ embeds_many :emb_twos, inverse_class_name: 'EmbTwo'
332
+
333
+ def self.name
334
+ 'ModelOne'
335
+ end
336
+ end
337
+ end
338
+
339
+ let(:emb_one) do
340
+ Class.new do
341
+ include Mongoid::Document
342
+ include Mongoid::History::Trackable
343
+ embedded_in :model_one
344
+
345
+ def self.name
346
+ 'EmbOne'
347
+ end
348
+ end
349
+ end
350
+
351
+ let(:emb_two) do
352
+ Class.new do
353
+ include Mongoid::Document
354
+ include Mongoid::History::Trackable
355
+ embedded_in :model_one
356
+
357
+ def self.name
358
+ 'EmbTwo'
359
+ end
360
+ end
361
+ end
362
+
363
+ let(:default_options) { { paranoia_field: 'deleted_at' } }
364
+
365
+ context 'when options not passed' do
366
+ before(:each) do
367
+ model_one.history_settings
368
+ emb_one.history_settings
369
+ emb_two.history_settings
370
+ end
371
+
372
+ it 'should use default options' do
373
+ expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(default_options)
374
+ expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(default_options)
375
+ expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(default_options)
376
+ end
377
+ end
378
+
379
+ context 'when extra invalid options passed' do
380
+ before(:each) do
381
+ model_one.history_settings foo: :bar
382
+ emb_one.history_settings em_foo: :em_bar
383
+ emb_two.history_settings em_foo: :em_baz
384
+ end
385
+
386
+ it 'should ignore invalid options' do
387
+ expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(default_options)
388
+ expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(default_options)
389
+ expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(default_options)
390
+ end
391
+ end
392
+
393
+ context 'when valid options passed' do
394
+ before(:each) do
395
+ model_one.history_settings paranoia_field: :disabled_at
396
+ emb_one.history_settings paranoia_field: :deactivated_at
397
+ emb_two.history_settings paranoia_field: :omitted_at
398
+ end
399
+
400
+ it 'should override default options' do
401
+ expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(paranoia_field: 'disabled_at')
402
+ expect(Mongoid::History.trackable_settings[:EmbOne]).to eq(paranoia_field: 'deactivated_at')
403
+ expect(Mongoid::History.trackable_settings[:EmbTwo]).to eq(paranoia_field: 'omitted_at')
404
+ end
405
+ end
406
+
407
+ context 'when string keys' do
408
+ before(:each) { model_one.history_settings 'paranoia_field' => 'erased_at' }
409
+
410
+ it 'should convert option keys to symbols' do
411
+ expect(Mongoid::History.trackable_settings[:ModelOne]).to eq(paranoia_field: 'erased_at')
412
+ end
413
+ end
414
+
415
+ context 'when paranoia field has alias' do
416
+ before(:each) do
417
+ Mongoid::History.trackable_settings = nil
418
+ model_two.history_settings paranoia_field: :neglected_at
419
+ end
420
+
421
+ let(:model_two) do
422
+ Class.new do
423
+ include Mongoid::Document
424
+ include Mongoid::History::Trackable
425
+ field :nglt, as: :neglected_at
426
+
427
+ def self.name
428
+ 'ModelTwo'
429
+ end
430
+ end
431
+ end
432
+
433
+ it { expect(Mongoid::History.trackable_settings[:ModelTwo]).to eq(paranoia_field: 'nglt') }
434
+ end
435
+ end
436
+
437
+ describe '#tracker_class' do
438
+ before :all do
439
+ MyTrackerClass = Class.new
440
+ end
441
+
442
+ before { MyModel.instance_variable_set(:@history_trackable_options, nil) }
443
+
444
+ context 'when options contain tracker_class_name' do
445
+ context 'when underscored' do
446
+ before { MyModel.track_history tracker_class_name: 'my_tracker_class' }
447
+ it { expect(MyModel.tracker_class).to eq MyTrackerClass }
448
+ end
449
+
450
+ context 'when camelcased' do
451
+ before { MyModel.track_history tracker_class_name: 'MyTrackerClass' }
452
+ it { expect(MyModel.tracker_class).to eq MyTrackerClass }
453
+ end
454
+
455
+ context 'when constant' do
456
+ before { MyModel.track_history tracker_class_name: MyTrackerClass }
457
+ it { expect(MyModel.tracker_class).to eq MyTrackerClass }
458
+ end
459
+ end
460
+
461
+ describe '#modified_attributes_for_update' do
462
+ before(:all) do
463
+ ModelOne = Class.new do
464
+ include Mongoid::Document
465
+ include Mongoid::History::Trackable
466
+ store_in collection: :model_ones
467
+ field :foo
468
+ embeds_many :emb_ones, inverse_class_name: 'EmbOne'
469
+ end
470
+
471
+ EmbOne = Class.new do
472
+ include Mongoid::Document
473
+ include Mongoid::History::Trackable
474
+ field :em_foo
475
+ embedded_in :model_one
476
+ end
477
+ end
478
+
479
+ before(:each) do
480
+ model_one.save!
481
+ ModelOne.instance_variable_set(:@history_trackable_options, nil)
482
+ ModelOne.instance_variable_set(:@trackable_settings, nil)
483
+ EmbOne.instance_variable_set(:@trackable_settings, nil)
484
+ end
485
+
486
+ let(:model_one) { ModelOne.new(foo: 'Foo') }
487
+ let(:changes) { {} }
488
+ subject { model_one.send(:modified_attributes_for_update) }
489
+
490
+ describe 'embeds_many' do
491
+ before(:each) { allow(model_one).to receive(:changes) { changes } }
492
+
493
+ context 'when not paranoia' do
494
+ before(:each) { ModelOne.track_history(on: :emb_ones) }
495
+ let(:changes) { { 'emb_ones' => [[{ 'em_foo' => 'Foo' }], [{ 'em_foo' => 'Foo-new' }]] } }
496
+ it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo' }] }
497
+ it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-new' }] }
498
+ end
499
+
500
+ context 'when default field for paranoia' do
501
+ before(:each) { ModelOne.track_history(on: :emb_ones) }
502
+ let(:changes) do
503
+ { 'emb_ones' => [[{ 'em_foo' => 'Foo' }, { 'em_foo' => 'Foo-2', 'deleted_at' => Time.now }],
504
+ [{ 'em_foo' => 'Foo-new' }, { 'em_foo' => 'Foo-2-new', 'deleted_at' => Time.now }]] }
505
+ end
506
+ it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo' }] }
507
+ it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-new' }] }
508
+ end
509
+
510
+ context 'when custom field for paranoia' do
511
+ before(:each) do
512
+ ModelOne.track_history on: :emb_ones
513
+ EmbOne.history_settings paranoia_field: :my_paranoia_field
514
+ end
515
+ let(:changes) do
516
+ { 'emb_ones' => [[{ 'em_foo' => 'Foo', 'my_paranoia_field' => Time.now },
517
+ { 'em_foo' => 'Foo-2' }],
518
+ [{ 'em_foo' => 'Foo-new', 'my_paranoia_field' => Time.now },
519
+ { 'em_foo' => 'Foo-2-new' }]] }
520
+ end
521
+ it { expect(subject['emb_ones'][0]).to eq [{ 'em_foo' => 'Foo-2' }] }
522
+ it { expect(subject['emb_ones'][1]).to eq [{ 'em_foo' => 'Foo-2-new' }] }
523
+ end
524
+ end
525
+
526
+ describe 'fields' do
527
+ context 'when custom method for changes' do
528
+ before(:each) do
529
+ ModelOne.track_history(on: :foo, changes_method: :my_changes_method)
530
+ allow(ModelOne).to receive(:dynamic_enabled?) { false }
531
+ allow(model_one).to receive(:my_changes_method) { changes }
532
+ end
533
+ let(:changes) { { 'foo' => ['Foo', 'Foo-new'], 'bar' => ['Bar', 'Bar-new'] } }
534
+ it { is_expected.to eq('foo' => ['Foo', 'Foo-new']) }
535
+ end
536
+ end
537
+
538
+ after(:all) do
539
+ Object.send(:remove_const, :ModelOne)
540
+ Object.send(:remove_const, :EmbOne)
541
+ end
542
+ end
543
+
544
+ context 'when options not contain tracker_class_name' do
545
+ before { MyModel.track_history }
546
+ it { expect(MyModel.tracker_class).to eq Tracker }
547
+ end
548
+
549
+ after :all do
550
+ Object.send(:remove_const, :MyTrackerClass)
284
551
  end
285
552
  end
286
553
  end