mongoid-history 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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