mongoid 6.4.1 → 6.4.8

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 (58) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/Rakefile +26 -0
  5. data/lib/mongoid.rb +1 -1
  6. data/lib/mongoid/contextual/map_reduce.rb +1 -1
  7. data/lib/mongoid/criteria/modifiable.rb +12 -2
  8. data/lib/mongoid/criteria/queryable/selectable.rb +34 -7
  9. data/lib/mongoid/document.rb +4 -4
  10. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  11. data/lib/mongoid/extensions/regexp.rb +1 -0
  12. data/lib/mongoid/extensions/string.rb +3 -1
  13. data/lib/mongoid/matchable.rb +3 -0
  14. data/lib/mongoid/matchable/nor.rb +37 -0
  15. data/lib/mongoid/persistable/settable.rb +5 -5
  16. data/lib/mongoid/persistence_context.rb +4 -0
  17. data/lib/mongoid/query_cache.rb +64 -19
  18. data/lib/mongoid/railtie.rb +17 -0
  19. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  20. data/lib/mongoid/scopable.rb +3 -3
  21. data/lib/mongoid/threaded.rb +36 -0
  22. data/lib/mongoid/version.rb +1 -1
  23. data/spec/app/models/array_field.rb +7 -0
  24. data/spec/app/models/delegating_patient.rb +16 -0
  25. data/spec/integration/document_spec.rb +22 -0
  26. data/spec/mongoid/clients/factory_spec.rb +52 -28
  27. data/spec/mongoid/clients/options_spec.rb +30 -15
  28. data/spec/mongoid/clients/sessions_spec.rb +12 -3
  29. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  30. data/spec/mongoid/contextual/mongo_spec.rb +2 -2
  31. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  32. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  33. data/spec/mongoid/criteria/queryable/selectable_spec.rb +42 -3
  34. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  35. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  36. data/spec/mongoid/criteria_spec.rb +4 -1
  37. data/spec/mongoid/document_spec.rb +54 -0
  38. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  39. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  40. data/spec/mongoid/extensions/string_spec.rb +35 -7
  41. data/spec/mongoid/fields_spec.rb +1 -1
  42. data/spec/mongoid/findable_spec.rb +1 -1
  43. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  44. data/spec/mongoid/matchable_spec.rb +26 -1
  45. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  46. data/spec/mongoid/persistable/settable_spec.rb +19 -0
  47. data/spec/mongoid/query_cache_spec.rb +87 -18
  48. data/spec/mongoid/scopable_spec.rb +13 -0
  49. data/spec/mongoid/threaded_spec.rb +68 -0
  50. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  51. data/spec/spec_helper.rb +10 -0
  52. data/spec/support/cluster_config.rb +158 -0
  53. data/spec/support/constraints.rb +101 -0
  54. data/spec/support/macros.rb +20 -0
  55. data/spec/support/session_registry.rb +50 -0
  56. data/spec/support/spec_config.rb +42 -0
  57. metadata +43 -23
  58. metadata.gz.sig +0 -0
@@ -16,17 +16,26 @@ describe Mongoid::Clients::Sessions do
16
16
  end
17
17
 
18
18
  let(:subscriber) do
19
- Mongoid::Clients.with_name(:other).instance_variable_get(:@monitoring).subscribers['Command'].find do |s|
19
+ client = Mongoid::Clients.with_name(:other)
20
+ monitoring = if client.respond_to?(:monitoring, true)
21
+ client.send(:monitoring)
22
+ else
23
+ # driver 2.5
24
+ client.instance_variable_get('@monitoring')
25
+ end
26
+ monitoring.subscribers['Command'].find do |s|
20
27
  s.is_a?(EventSubscriber)
21
28
  end
22
29
  end
23
30
 
24
31
  let(:insert_events) do
25
- subscriber.started_events.select { |event| event.command_name == :insert }
32
+ # Driver 2.5 sends command_name as a symbol
33
+ subscriber.started_events.select { |event| event.command_name.to_s == 'insert' }
26
34
  end
27
35
 
28
36
  let(:update_events) do
29
- subscriber.started_events.select { |event| event.command_name == :update }
37
+ # Driver 2.5 sends command_name as a symbol
38
+ subscriber.started_events.select { |event| event.command_name.to_s == 'update' }
30
39
  end
31
40
 
32
41
  context 'when a session is used on a model class' do
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Mongoid::Contextual::GeoNear do
4
+ max_server_version '4.0'
4
5
 
5
6
  describe "#average_distance" do
6
7
 
@@ -451,7 +451,7 @@ describe Mongoid::Contextual::Mongo do
451
451
  end
452
452
 
453
453
  it "returns the distinct field values" do
454
- expect(context.distinct(:years)).to eq([ 30, 25 ])
454
+ expect(context.distinct(:years).sort).to eq([ 25, 30 ])
455
455
  end
456
456
  end
457
457
 
@@ -2363,7 +2363,7 @@ describe Mongoid::Contextual::Mongo do
2363
2363
  end
2364
2364
 
2365
2365
  it 'creates a pipeline with the selector as one of the $match criteria' do
2366
- expect(pipeline_match).to include({ :'$text' => { :'$search' => "New Order" } })
2366
+ expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
2367
2367
  end
2368
2368
 
2369
2369
  it 'creates a pipeline with the $exists operator as one of the $match criteria' do
@@ -1471,11 +1471,15 @@ describe Mongoid::Criteria::Modifiable do
1471
1471
  { 'username' => 'Turnip' }
1472
1472
  end
1473
1473
 
1474
- it 'returns a criteria with the defined attributes' do
1475
- expect(Person.create_with(attrs).selector).to eq(attrs)
1474
+ it 'does not modify the selector' do
1475
+ expect(Person.create_with(attrs).selector[:username]).to be_nil
1476
1476
  end
1477
1477
 
1478
- context 'when a method is chained' do
1478
+ it 'create_attrs is modified' do
1479
+ expect(Person.create_with(attrs).create_attrs).to eq(attrs)
1480
+ end
1481
+
1482
+ context 'when a create is chained' do
1479
1483
 
1480
1484
  context 'when a write method is chained' do
1481
1485
 
@@ -1499,6 +1503,25 @@ describe Mongoid::Criteria::Modifiable do
1499
1503
  expect(new_person.age).to eq(50)
1500
1504
  end
1501
1505
 
1506
+ context 'when a matching document is already in the collection' do
1507
+ let(:query) do
1508
+ { 'username' => 'foo', 'age' => 12 }
1509
+ end
1510
+
1511
+ let(:person) do
1512
+ Person.create!(query)
1513
+ end
1514
+
1515
+ let(:found_person) do
1516
+ Person.create_with(attrs).find_or_create_by(query)
1517
+ end
1518
+
1519
+ it 'finds the matching document' do
1520
+ person
1521
+ expect(found_person.id).to eq(person.id)
1522
+ end
1523
+ end
1524
+
1502
1525
  context 'when the attributes are shared with the write method args' do
1503
1526
 
1504
1527
  let(:query) do
@@ -1509,7 +1532,7 @@ describe Mongoid::Criteria::Modifiable do
1509
1532
  Person.create_with(attrs).find_or_create_by(query)
1510
1533
  end
1511
1534
 
1512
- it 'gives the write method args precedence' do
1535
+ it 'gives the find method args precedence' do
1513
1536
  expect(new_person.username).to eq('Beet')
1514
1537
  expect(new_person.age).to eq(50)
1515
1538
  end
@@ -1536,8 +1559,12 @@ describe Mongoid::Criteria::Modifiable do
1536
1559
  { 'username' => 'Beet', 'age' => 50 }
1537
1560
  end
1538
1561
 
1562
+ it 'does not modify the selector' do
1563
+ expect(criteria.create_with(attrs).selector).to eq(criteria_selector)
1564
+ end
1565
+
1539
1566
  it 'overwrites all the original attributes' do
1540
- expect(criteria.create_with(attrs).selector).to eq(attrs)
1567
+ expect(criteria.create_with(attrs).create_attrs).to eq(attrs)
1541
1568
  end
1542
1569
  end
1543
1570
  end
@@ -1548,8 +1575,12 @@ describe Mongoid::Criteria::Modifiable do
1548
1575
  { 'username' => 'Beet' }
1549
1576
  end
1550
1577
 
1578
+ it 'does not modify the selector' do
1579
+ expect(criteria.create_with(attrs).selector).to eq(criteria_selector)
1580
+ end
1581
+
1551
1582
  it 'only overwrites the shared attributes' do
1552
- expect(criteria.create_with(attrs).selector).to eq(criteria_selector.merge!(attrs))
1583
+ expect(criteria.create_with(attrs).create_attrs).to eq(attrs)
1553
1584
  end
1554
1585
  end
1555
1586
 
@@ -1558,12 +1589,11 @@ describe Mongoid::Criteria::Modifiable do
1558
1589
  let(:attrs) do
1559
1590
  { 'username' => 'Turnip' }
1560
1591
  end
1561
-
1562
1592
  let(:query) do
1563
1593
  { 'username' => 'Beet', 'age' => 50 }
1564
1594
  end
1565
1595
 
1566
- context 'when a write method is chained' do
1596
+ context 'when a create method is chained' do
1567
1597
 
1568
1598
  it 'executes the method' do
1569
1599
  expect(criteria.create_with(attrs).new.username).to eq('Turnip')
@@ -1577,9 +1607,28 @@ describe Mongoid::Criteria::Modifiable do
1577
1607
  criteria.create_with(attrs).find_or_create_by(query)
1578
1608
  end
1579
1609
 
1580
- it 'executes the query' do
1610
+ it 'gives the find method arg precedence' do
1581
1611
  expect(new_person.username).to eq('Beet')
1582
- expect(new_person.age).to eq(50)
1612
+ expect(new_person.age).to be(50)
1613
+ end
1614
+
1615
+ context 'when a matching document is already in the collection' do
1616
+ let(:query) do
1617
+ { 'username' => 'foo', 'age' => 12 }
1618
+ end
1619
+
1620
+ let(:person) do
1621
+ Person.create!(query)
1622
+ end
1623
+
1624
+ let(:found_person) do
1625
+ criteria.create_with(attrs).find_or_create_by(query)
1626
+ end
1627
+
1628
+ it 'finds the matching document' do
1629
+ person
1630
+ expect(found_person.id).to eq(person.id)
1631
+ end
1583
1632
  end
1584
1633
  end
1585
1634
  end
@@ -7,7 +7,7 @@ describe BigDecimal do
7
7
  context "when provided a big decimal" do
8
8
 
9
9
  let(:big_decimal) do
10
- BigDecimal.new("123456.789")
10
+ BigDecimal("123456.789")
11
11
  end
12
12
 
13
13
  it "returns the decimal as a string" do
@@ -25,11 +25,11 @@ describe BigDecimal do
25
25
  context "when provided an array of big decimals" do
26
26
 
27
27
  let(:bd_one) do
28
- BigDecimal.new("123456.789")
28
+ BigDecimal("123456.789")
29
29
  end
30
30
 
31
31
  let(:bd_two) do
32
- BigDecimal.new("123333.789")
32
+ BigDecimal("123333.789")
33
33
  end
34
34
 
35
35
  let(:array) do
@@ -1073,6 +1073,26 @@ describe Mongoid::Criteria::Queryable::Selectable do
1073
1073
  })
1074
1074
  end
1075
1075
 
1076
+ context "when used with the $box operator ($geoWithin query) " do
1077
+ let(:selection) do
1078
+ query.geo_spacial(
1079
+ :location.within_box => [[ 1, 10 ], [ 2, 10 ]]
1080
+ )
1081
+ end
1082
+
1083
+ it "adds the $geoIntersects expression" do
1084
+ expect(selection.selector).to eq({
1085
+ "location" => {
1086
+ "$geoWithin" => {
1087
+ "$box" => [
1088
+ [ 1, 10 ], [ 2, 10 ]
1089
+ ]
1090
+ }
1091
+ }
1092
+ })
1093
+ end
1094
+ end
1095
+
1076
1096
  it_behaves_like "a cloning selection"
1077
1097
  end
1078
1098
  end
@@ -3369,7 +3389,7 @@ describe Mongoid::Criteria::Queryable::Selectable do
3369
3389
  end
3370
3390
 
3371
3391
  it "constructs a text search document" do
3372
- expect(selection.selector).to eq({ :$text => { :$search => "testing" }})
3392
+ expect(selection.selector).to eq({ '$text' => { '$search' => "testing" }})
3373
3393
  end
3374
3394
 
3375
3395
  it "returns the cloned selectable" do
@@ -3383,16 +3403,35 @@ describe Mongoid::Criteria::Queryable::Selectable do
3383
3403
  end
3384
3404
 
3385
3405
  it "constructs a text search document" do
3386
- expect(selection.selector[:$text][:$search]).to eq("essais")
3406
+ expect(selection.selector['$text']['$search']).to eq("essais")
3387
3407
  end
3388
3408
 
3389
3409
  it "add the options to the text search document" do
3390
- expect(selection.selector[:$text][:$language]).to eq("fr")
3410
+ expect(selection.selector['$text'][:$language]).to eq("fr")
3391
3411
  end
3392
3412
 
3393
3413
  it_behaves_like "a cloning selection"
3394
3414
  end
3395
3415
  end
3416
+
3417
+ context 'when given more than once' do
3418
+ let(:selection) do
3419
+ query.text_search("one").text_search('two')
3420
+ end
3421
+
3422
+ # MongoDB server can only handle one text expression at a time,
3423
+ # per https://docs.mongodb.com/manual/reference/operator/query/text/.
3424
+ # Nonetheless we test that the query is built correctly when
3425
+ # a user supplies more than one text condition.
3426
+ it 'merges conditions' do
3427
+ expect(Mongoid.logger).to receive(:warn)
3428
+ expect(selection.selector).to eq('$and' => [
3429
+ {'$text' => {'$search' => 'one'}}
3430
+ ],
3431
+ '$text' => {'$search' => 'two'},
3432
+ )
3433
+ end
3434
+ end
3396
3435
  end
3397
3436
 
3398
3437
  describe "#where" do
@@ -370,11 +370,11 @@ describe Mongoid::Criteria::Queryable::Selector do
370
370
  context "when providing an array" do
371
371
 
372
372
  let(:big_one) do
373
- BigDecimal.new("1.2321")
373
+ BigDecimal("1.2321")
374
374
  end
375
375
 
376
376
  let(:big_two) do
377
- BigDecimal.new("4.2222")
377
+ BigDecimal("4.2222")
378
378
  end
379
379
 
380
380
  let(:array) do
@@ -388,4 +388,85 @@ describe Mongoid::Criteria::Scopable do
388
388
  end
389
389
  end
390
390
  end
391
+
392
+ describe 'scope and where' do
393
+ class ScopeWhereModel
394
+ include Mongoid::Document
395
+
396
+ scope :foo, -> { where(foo: true) }
397
+ end
398
+
399
+ shared_examples_for 'restricts to both' do
400
+ it 'restricts to both' do
401
+ expect(result.selector['foo']).to eq(true)
402
+ expect(result.selector['hello']).to eq('world')
403
+ end
404
+ end
405
+
406
+ context 'scope is given first' do
407
+ let(:result) { ScopeWhereModel.foo.where(hello: 'world') }
408
+
409
+ it_behaves_like 'restricts to both'
410
+ end
411
+
412
+ context 'where is given first' do
413
+ let(:result) { ScopeWhereModel.where(hello: 'world').foo }
414
+
415
+ it_behaves_like 'restricts to both'
416
+ end
417
+ end
418
+
419
+ describe 'scope with argument and where' do
420
+ class ArgumentScopeWhereModel
421
+ include Mongoid::Document
422
+
423
+ scope :my_text_search, ->(search) { where('$text' => {'$search' => search}) }
424
+ end
425
+
426
+ shared_examples_for 'restricts to both' do
427
+ it 'restricts to both' do
428
+ expect(result.selector['$text']).to eq({'$search' => 'bar'})
429
+ expect(result.selector['hello']).to eq('world')
430
+ end
431
+ end
432
+
433
+ context 'scope is given first' do
434
+ let(:result) { ArgumentScopeWhereModel.my_text_search('bar').where(hello: 'world') }
435
+
436
+ it_behaves_like 'restricts to both'
437
+ end
438
+
439
+ context 'where is given first' do
440
+ let(:result) { ArgumentScopeWhereModel.where(hello: 'world').my_text_search('bar') }
441
+
442
+ it_behaves_like 'restricts to both'
443
+ end
444
+ end
445
+
446
+ describe 'built in text search scope and where' do
447
+ class TextSearchScopeWhereModel
448
+ include Mongoid::Document
449
+
450
+ # using the default text_search scope
451
+ end
452
+
453
+ shared_examples_for 'restricts to both' do
454
+ it 'restricts to both' do
455
+ expect(result.selector['$text']).to eq({'$search' => 'bar'})
456
+ expect(result.selector['hello']).to eq('world')
457
+ end
458
+ end
459
+
460
+ context 'scope is given first' do
461
+ let(:result) { TextSearchScopeWhereModel.text_search('bar').where(hello: 'world') }
462
+
463
+ it_behaves_like 'restricts to both'
464
+ end
465
+
466
+ context 'where is given first' do
467
+ let(:result) { TextSearchScopeWhereModel.where(hello: 'world').text_search('bar') }
468
+
469
+ it_behaves_like 'restricts to both'
470
+ end
471
+ end
391
472
  end
@@ -975,6 +975,7 @@ describe Mongoid::Criteria do
975
975
  end
976
976
 
977
977
  describe "#geo_near" do
978
+ max_server_version '4.0'
978
979
 
979
980
  before do
980
981
  Bar.create_indexes
@@ -3321,6 +3322,8 @@ describe Mongoid::Criteria do
3321
3322
  end
3322
3323
 
3323
3324
  describe "#max_scan" do
3325
+ max_server_version '4.0'
3326
+
3324
3327
  let!(:band) do
3325
3328
  Band.create(name: "Depeche Mode")
3326
3329
  end
@@ -3465,7 +3468,7 @@ describe Mongoid::Criteria do
3465
3468
  context "when querying on a big decimal" do
3466
3469
 
3467
3470
  let(:sales) do
3468
- BigDecimal.new('0.1')
3471
+ BigDecimal('0.1')
3469
3472
  end
3470
3473
 
3471
3474
  let!(:band) do
@@ -433,6 +433,60 @@ describe Mongoid::Document do
433
433
  end
434
434
  end
435
435
  end
436
+
437
+ context ':compact option' do
438
+
439
+ before do
440
+ # These tests require a specific set of defined attributes
441
+ # on the model
442
+ expect(church.as_json.keys.sort).to eq(%w(_id location name))
443
+ end
444
+
445
+ context 'there is a nil valued attribute' do
446
+ let(:church) do
447
+ Church.create!(name: 'St. Basil')
448
+ end
449
+
450
+ it 'returns non-nil fields and _id only' do
451
+ actual = church.as_json(compact: true)
452
+ expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil')
453
+ end
454
+ end
455
+
456
+ context 'all attrbutes are nil valued' do
457
+ let(:church) do
458
+ Church.create!
459
+ end
460
+
461
+ it 'returns a hash with _id only' do
462
+ actual = church.as_json(compact: true)
463
+ expect(actual).to eq('_id' => church.id)
464
+ end
465
+ end
466
+
467
+ context 'there are no nil valued attributes' do
468
+ let(:church) do
469
+ Church.create!(name: 'St. Basil', location: {})
470
+ end
471
+
472
+ it 'returns all fields and _id' do
473
+ actual = church.as_json(compact: true)
474
+ expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil',
475
+ 'location' => {})
476
+ end
477
+ end
478
+
479
+ context 'when option is specified as a truthy value' do
480
+ let(:church) do
481
+ Church.create!(name: 'St. Basil')
482
+ end
483
+
484
+ it 'returns non-nil fields and _id only' do
485
+ actual = church.as_json(compact: 1)
486
+ expect(actual).to eq('_id' => church.id, 'name' => 'St. Basil')
487
+ end
488
+ end
489
+ end
436
490
  end
437
491
 
438
492
  describe "#as_document" do