mongoid 9.1.0 → 9.1.2

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +69 -0
  3. data/lib/mongoid/association/accessors.rb +27 -5
  4. data/lib/mongoid/association/depending.rb +14 -10
  5. data/lib/mongoid/association/eager.rb +9 -0
  6. data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
  7. data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
  8. data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
  9. data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
  10. data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
  11. data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
  12. data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
  13. data/lib/mongoid/association/eager_loadable.rb +65 -101
  14. data/lib/mongoid/association/embedded/batchable.rb +17 -1
  15. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
  16. data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
  17. data/lib/mongoid/association/macros.rb +13 -1
  18. data/lib/mongoid/association/nested/many.rb +34 -5
  19. data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
  20. data/lib/mongoid/association/nested/one.rb +6 -1
  21. data/lib/mongoid/association/options.rb +17 -0
  22. data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
  23. data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
  24. data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
  25. data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
  26. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
  27. data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
  28. data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
  29. data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
  30. data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
  31. data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
  32. data/lib/mongoid/association/referenced/has_one.rb +1 -0
  33. data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
  34. data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
  35. data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
  36. data/lib/mongoid/association/referenced.rb +2 -0
  37. data/lib/mongoid/association/relatable.rb +58 -0
  38. data/lib/mongoid/association.rb +15 -0
  39. data/lib/mongoid/atomic/modifiers.rb +100 -16
  40. data/lib/mongoid/clients/factory.rb +9 -13
  41. data/lib/mongoid/collection_configurable.rb +8 -0
  42. data/lib/mongoid/config/defaults.rb +0 -1
  43. data/lib/mongoid/config/encryption.rb +170 -22
  44. data/lib/mongoid/config.rb +56 -5
  45. data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
  46. data/lib/mongoid/contextual/memory.rb +18 -7
  47. data/lib/mongoid/criteria/includable.rb +35 -11
  48. data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
  49. data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
  50. data/lib/mongoid/deprecable.rb +45 -2
  51. data/lib/mongoid/document.rb +3 -0
  52. data/lib/mongoid/encryptable.rb +73 -0
  53. data/lib/mongoid/errors/config_redactor.rb +43 -0
  54. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
  55. data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
  56. data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
  57. data/lib/mongoid/errors/no_client_database.rb +1 -1
  58. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  59. data/lib/mongoid/errors/no_encryption_schema.rb +30 -0
  60. data/lib/mongoid/errors/readonly_association.rb +29 -0
  61. data/lib/mongoid/errors.rb +5 -0
  62. data/lib/mongoid/extensions/vector.rb +64 -0
  63. data/lib/mongoid/extensions.rb +1 -0
  64. data/lib/mongoid/field_readable.rb +69 -0
  65. data/lib/mongoid/fields.rb +7 -0
  66. data/lib/mongoid/indexable.rb +38 -26
  67. data/lib/mongoid/matchable.rb +6 -1
  68. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
  69. data/lib/mongoid/matcher/regex.rb +11 -12
  70. data/lib/mongoid/matcher/regexp_budget.rb +372 -0
  71. data/lib/mongoid/matcher.rb +1 -0
  72. data/lib/mongoid/persistable/creatable.rb +120 -14
  73. data/lib/mongoid/persistable/updatable.rb +9 -11
  74. data/lib/mongoid/persistable.rb +15 -15
  75. data/lib/mongoid/persistence_context.rb +77 -0
  76. data/lib/mongoid/search_indexable.rb +69 -24
  77. data/lib/mongoid/serializable.rb +12 -4
  78. data/lib/mongoid/tasks/database.rb +33 -16
  79. data/lib/mongoid/threaded.rb +36 -0
  80. data/lib/mongoid/timestamps/timeless.rb +84 -10
  81. data/lib/mongoid/touchable.rb +28 -1
  82. data/lib/mongoid/traversable.rb +2 -2
  83. data/lib/mongoid/validatable/associated.rb +18 -2
  84. data/lib/mongoid/version.rb +1 -1
  85. data/lib/mongoid/warnings.rb +4 -0
  86. data/spec/integration/app_spec.rb +7 -1
  87. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  88. data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
  89. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +31 -2
  90. data/spec/integration/atomic/modifiers_spec.rb +283 -0
  91. data/spec/integration/dots_and_dollars_spec.rb +12 -2
  92. data/spec/integration/encryption_spec.rb +300 -0
  93. data/spec/integration/matcher_operator_data/regex.yml +21 -0
  94. data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
  95. data/spec/integration/query_operator_guard_spec.rb +89 -0
  96. data/spec/mongoid/association/accessors_spec.rb +35 -0
  97. data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
  98. data/spec/mongoid/association/eager_spec.rb +2 -2
  99. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
  100. data/spec/mongoid/association/fallback_spec.rb +425 -0
  101. data/spec/mongoid/association/macros_spec.rb +26 -0
  102. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
  103. data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
  104. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
  105. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
  106. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
  107. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
  108. data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
  109. data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
  110. data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
  111. data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
  112. data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
  113. data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
  114. data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
  115. data/spec/mongoid/attributes/nested_spec.rb +278 -8
  116. data/spec/mongoid/clients/factory_spec.rb +36 -0
  117. data/spec/mongoid/config/defaults_spec.rb +33 -0
  118. data/spec/mongoid/config/encryption_spec.rb +451 -0
  119. data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
  120. data/spec/mongoid/contextual/memory_spec.rb +177 -0
  121. data/spec/mongoid/criteria/includable_spec.rb +1221 -0
  122. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
  123. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
  124. data/spec/mongoid/criteria_spec.rb +3 -0
  125. data/spec/mongoid/encryptable_spec.rb +115 -0
  126. data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
  127. data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
  128. data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
  129. data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
  130. data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
  131. data/spec/mongoid/extensions/vector_spec.rb +175 -0
  132. data/spec/mongoid/fields_spec.rb +8 -0
  133. data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
  134. data/spec/mongoid/search_indexable_spec.rb +321 -9
  135. data/spec/mongoid/tasks/database_spec.rb +31 -0
  136. data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
  137. data/spec/mongoid/touchable_spec.rb +242 -0
  138. data/spec/mongoid/touchable_spec_models.rb +47 -0
  139. data/spec/mongoid/validatable/associated_spec.rb +19 -0
  140. data/spec/spec_helper.rb +5 -1
  141. data/spec/support/crypt/models.rb +253 -0
  142. data/spec/support/models/modifier_models.rb +65 -0
  143. metadata +55 -2
@@ -126,8 +126,11 @@ describe Mongoid::Criteria::Includable do
126
126
  end
127
127
 
128
128
  after(:all) do
129
+ Mongoid.deregister_model(A)
129
130
  Object.send(:remove_const, :A)
131
+ Mongoid.deregister_model(B)
130
132
  Object.send(:remove_const, :B)
133
+ Mongoid.deregister_model(C)
131
134
  Object.send(:remove_const, :C)
132
135
  end
133
136
 
@@ -186,9 +189,13 @@ describe Mongoid::Criteria::Includable do
186
189
  end
187
190
 
188
191
  after(:all) do
192
+ Mongoid.deregister_model(A)
189
193
  Object.send(:remove_const, :A)
194
+ Mongoid.deregister_model(B)
190
195
  Object.send(:remove_const, :B)
196
+ Mongoid.deregister_model(C)
191
197
  Object.send(:remove_const, :C)
198
+ Mongoid.deregister_model(D)
192
199
  Object.send(:remove_const, :D)
193
200
  end
194
201
 
@@ -255,9 +262,13 @@ describe Mongoid::Criteria::Includable do
255
262
  end
256
263
 
257
264
  after(:all) do
265
+ Mongoid.deregister_model(A)
258
266
  Object.send(:remove_const, :A)
267
+ Mongoid.deregister_model(B)
259
268
  Object.send(:remove_const, :B)
269
+ Mongoid.deregister_model(C)
260
270
  Object.send(:remove_const, :C)
271
+ Mongoid.deregister_model(D)
261
272
  Object.send(:remove_const, :D)
262
273
  end
263
274
 
@@ -1121,9 +1132,13 @@ describe Mongoid::Criteria::Includable do
1121
1132
  end
1122
1133
 
1123
1134
  after(:all) do
1135
+ Mongoid.deregister_model(A)
1124
1136
  Object.send(:remove_const, :A)
1137
+ Mongoid.deregister_model(B)
1125
1138
  Object.send(:remove_const, :B)
1139
+ Mongoid.deregister_model(C)
1126
1140
  Object.send(:remove_const, :C)
1141
+ Mongoid.deregister_model(D)
1127
1142
  Object.send(:remove_const, :D)
1128
1143
  end
1129
1144
 
@@ -1408,12 +1423,1218 @@ describe Mongoid::Criteria::Includable do
1408
1423
 
1409
1424
  describe '#includes' do
1410
1425
  it_behaves_like 'eager loading', :includes
1426
+
1427
+ context 'when a nested association sits behind a parent whose class_name is not the resolved class name' do
1428
+ before(:all) do
1429
+ class IncEagerGallery
1430
+ include Mongoid::Document
1431
+
1432
+ has_many :rooted_photos, class_name: '::IncEagerPhoto', inverse_of: :gallery
1433
+ end
1434
+
1435
+ class IncEagerPhoto
1436
+ include Mongoid::Document
1437
+
1438
+ belongs_to :gallery, class_name: 'IncEagerGallery', inverse_of: :rooted_photos
1439
+ belongs_to :author, class_name: '::IncEagerAuthor'
1440
+ end
1441
+
1442
+ class IncEagerAuthor
1443
+ include Mongoid::Document
1444
+ end
1445
+
1446
+ module IncEagerShop
1447
+ class Order
1448
+ include Mongoid::Document
1449
+
1450
+ has_many :items, class_name: 'Item', inverse_of: :order
1451
+ end
1452
+
1453
+ class Item
1454
+ include Mongoid::Document
1455
+
1456
+ belongs_to :order, class_name: 'Order', inverse_of: :items
1457
+ belongs_to :author, class_name: '::IncEagerAuthor'
1458
+ end
1459
+ end
1460
+
1461
+ class IncEagerAlbum
1462
+ include Mongoid::Document
1463
+
1464
+ has_many :poly_photos, class_name: '::IncEagerPolyPhoto', as: :imageable, inverse_of: :imageable
1465
+ end
1466
+
1467
+ class IncEagerPolyPhoto
1468
+ include Mongoid::Document
1469
+
1470
+ belongs_to :imageable, polymorphic: true, inverse_of: :poly_photos
1471
+ belongs_to :author, class_name: '::IncEagerAuthor'
1472
+ end
1473
+ end
1474
+
1475
+ after(:all) do
1476
+ Mongoid.deregister_model(IncEagerAlbum)
1477
+ Object.send(:remove_const, :IncEagerAlbum)
1478
+ Mongoid.deregister_model(IncEagerPolyPhoto)
1479
+ Object.send(:remove_const, :IncEagerPolyPhoto)
1480
+ Mongoid.deregister_model(IncEagerShop::Order)
1481
+ Mongoid.deregister_model(IncEagerShop::Item)
1482
+ Object.send(:remove_const, :IncEagerShop)
1483
+ Mongoid.deregister_model(IncEagerAuthor)
1484
+ Object.send(:remove_const, :IncEagerAuthor)
1485
+ Mongoid.deregister_model(IncEagerPhoto)
1486
+ Object.send(:remove_const, :IncEagerPhoto)
1487
+ Mongoid.deregister_model(IncEagerGallery)
1488
+ Object.send(:remove_const, :IncEagerGallery)
1489
+ end
1490
+
1491
+ let!(:gallery) { IncEagerGallery.create! }
1492
+ let!(:order) { IncEagerShop::Order.create! }
1493
+
1494
+ before do
1495
+ IncEagerPhoto.create!(gallery: gallery, author: IncEagerAuthor.create!)
1496
+ IncEagerShop::Item.create!(order: order, author: IncEagerAuthor.create!)
1497
+ end
1498
+
1499
+ it 'eager loads a nested association behind a class_name with a leading ::' do
1500
+ result = IncEagerGallery.includes(rooted_photos: :author).first
1501
+
1502
+ expect_query(0) do
1503
+ expect(result.rooted_photos.first.author).to be_a(IncEagerAuthor)
1504
+ end
1505
+ end
1506
+
1507
+ it 'eager loads a nested association behind a module-relative class_name' do
1508
+ result = IncEagerShop::Order.includes(items: :author).first
1509
+
1510
+ expect_query(0) do
1511
+ expect(result.items.first.author).to be_a(IncEagerAuthor)
1512
+ end
1513
+ end
1514
+
1515
+ it 'eager loads a nested association behind a polymorphic parent with a leading ::' do
1516
+ album = IncEagerAlbum.create!
1517
+ IncEagerPolyPhoto.create!(imageable: album, author: IncEagerAuthor.create!)
1518
+
1519
+ result = IncEagerAlbum.includes(poly_photos: :author).first
1520
+
1521
+ expect_query(0) do
1522
+ expect(result.poly_photos.first.author).to be_a(IncEagerAuthor)
1523
+ end
1524
+ end
1525
+ end
1411
1526
  end
1412
1527
 
1413
1528
  describe '#eager_load' do
1414
1529
  # 4.4 lookup does not support the lookup pipeline as it is currently written
1415
1530
  min_server_version '5.0'
1416
1531
  it_behaves_like 'eager loading', :eager_load
1532
+
1533
+ context 'when the inclusion list is empty' do
1534
+ let!(:band) { Band.create! }
1535
+
1536
+ it 'returns the documents without eager loading' do
1537
+ expect(Band.eager_load([]).first).to eq(band)
1538
+ end
1539
+ end
1540
+
1541
+ context 'when the association is defined on a subclass of the queried class' do
1542
+ before(:all) do
1543
+ class Machine
1544
+ include Mongoid::Document
1545
+ end
1546
+
1547
+ class Peripheral
1548
+ include Mongoid::Document
1549
+ end
1550
+
1551
+ class Webcam < Peripheral
1552
+ end
1553
+
1554
+ class Mouse < Peripheral
1555
+ belongs_to :machine
1556
+ end
1557
+ end
1558
+
1559
+ after(:all) do
1560
+ Mongoid.deregister_model(Mouse)
1561
+ Object.send(:remove_const, :Mouse)
1562
+ Mongoid.deregister_model(Webcam)
1563
+ Object.send(:remove_const, :Webcam)
1564
+ Mongoid.deregister_model(Peripheral)
1565
+ Object.send(:remove_const, :Peripheral)
1566
+ Mongoid.deregister_model(Machine)
1567
+ Object.send(:remove_const, :Machine)
1568
+ end
1569
+
1570
+ let!(:machine) { Machine.create! }
1571
+ let!(:webcam) { Webcam.create! }
1572
+ let!(:mouse) { Mouse.create!(machine: machine) }
1573
+
1574
+ it 'eager-loads it through the queried superclass' do
1575
+ expect_query(1) do
1576
+ loaded = Peripheral.eager_load(:machine).detect { |doc| doc.is_a?(Mouse) }
1577
+ expect(loaded.machine).to eq(machine)
1578
+ end
1579
+ end
1580
+ end
1581
+
1582
+ context 'when the association is defined on a subclass of an associated class' do
1583
+ before(:all) do
1584
+ class Brand
1585
+ include Mongoid::Document
1586
+ end
1587
+
1588
+ class Machine
1589
+ include Mongoid::Document
1590
+ end
1591
+
1592
+ class Laptop < Machine
1593
+ belongs_to :brand
1594
+ end
1595
+
1596
+ class Peripheral
1597
+ include Mongoid::Document
1598
+ end
1599
+
1600
+ class Mouse < Peripheral
1601
+ belongs_to :machine
1602
+ end
1603
+ end
1604
+
1605
+ after(:all) do
1606
+ Mongoid.deregister_model(Mouse)
1607
+ Object.send(:remove_const, :Mouse)
1608
+ Mongoid.deregister_model(Peripheral)
1609
+ Object.send(:remove_const, :Peripheral)
1610
+ Mongoid.deregister_model(Laptop)
1611
+ Object.send(:remove_const, :Laptop)
1612
+ Mongoid.deregister_model(Machine)
1613
+ Object.send(:remove_const, :Machine)
1614
+ Mongoid.deregister_model(Brand)
1615
+ Object.send(:remove_const, :Brand)
1616
+ end
1617
+
1618
+ let!(:brand) { Brand.create! }
1619
+ let!(:laptop) { Laptop.create!(brand: brand) }
1620
+ let!(:mouse) { Mouse.create!(machine: laptop) }
1621
+
1622
+ it 'eager-loads it through the parent association' do
1623
+ expect_query(1) do
1624
+ loaded = Peripheral.eager_load(machine: :brand).first
1625
+ expect(loaded.machine).to eq(laptop)
1626
+ expect(loaded.machine.brand).to eq(brand)
1627
+ end
1628
+ end
1629
+ end
1630
+
1631
+ context 'when a has_many is defined only on a subclass' do
1632
+ before(:all) do
1633
+ class Gadget
1634
+ include Mongoid::Document
1635
+ end
1636
+
1637
+ class Speaker < Gadget
1638
+ has_many :cables
1639
+ end
1640
+
1641
+ class Cable
1642
+ include Mongoid::Document
1643
+
1644
+ belongs_to :speaker
1645
+ end
1646
+ end
1647
+
1648
+ after(:all) do
1649
+ Mongoid.deregister_model(Cable)
1650
+ Object.send(:remove_const, :Cable)
1651
+ Mongoid.deregister_model(Speaker)
1652
+ Object.send(:remove_const, :Speaker)
1653
+ Mongoid.deregister_model(Gadget)
1654
+ Object.send(:remove_const, :Gadget)
1655
+ end
1656
+
1657
+ let!(:speaker) { Speaker.create! }
1658
+ let!(:cable) { Cable.create!(speaker: speaker) }
1659
+
1660
+ it 'eager-loads it through the queried superclass' do
1661
+ expect_query(1) do
1662
+ loaded = Gadget.eager_load(:cables).first
1663
+ expect(loaded.cables).to eq([ cable ])
1664
+ end
1665
+ end
1666
+ end
1667
+
1668
+ context 'when a has_one is defined only on a subclass' do
1669
+ before(:all) do
1670
+ class Gadget
1671
+ include Mongoid::Document
1672
+ end
1673
+
1674
+ class Speaker < Gadget
1675
+ has_one :cable
1676
+ end
1677
+
1678
+ class Cable
1679
+ include Mongoid::Document
1680
+
1681
+ belongs_to :speaker
1682
+ end
1683
+ end
1684
+
1685
+ after(:all) do
1686
+ Mongoid.deregister_model(Cable)
1687
+ Object.send(:remove_const, :Cable)
1688
+ Mongoid.deregister_model(Speaker)
1689
+ Object.send(:remove_const, :Speaker)
1690
+ Mongoid.deregister_model(Gadget)
1691
+ Object.send(:remove_const, :Gadget)
1692
+ end
1693
+
1694
+ let!(:speaker) { Speaker.create! }
1695
+ let!(:cable) { Cable.create!(speaker: speaker) }
1696
+
1697
+ it 'eager-loads it through the queried superclass' do
1698
+ expect_query(1) do
1699
+ loaded = Gadget.eager_load(:cable).first
1700
+ expect(loaded.cable).to eq(cable)
1701
+ end
1702
+ end
1703
+ end
1704
+
1705
+ context 'when a has_and_belongs_to_many is defined only on a subclass' do
1706
+ before(:all) do
1707
+ class Cable
1708
+ include Mongoid::Document
1709
+
1710
+ has_and_belongs_to_many :speakers
1711
+ end
1712
+
1713
+ class Gadget
1714
+ include Mongoid::Document
1715
+ end
1716
+
1717
+ class Speaker < Gadget
1718
+ has_and_belongs_to_many :cables
1719
+ end
1720
+ end
1721
+
1722
+ after(:all) do
1723
+ Mongoid.deregister_model(Speaker)
1724
+ Object.send(:remove_const, :Speaker)
1725
+ Mongoid.deregister_model(Gadget)
1726
+ Object.send(:remove_const, :Gadget)
1727
+ Mongoid.deregister_model(Cable)
1728
+ Object.send(:remove_const, :Cable)
1729
+ end
1730
+
1731
+ let!(:cable) { Cable.create! }
1732
+ let!(:speaker) { Speaker.create!(cables: [ cable ]) }
1733
+
1734
+ it 'eager-loads it through the queried superclass' do
1735
+ expect_query(1) do
1736
+ loaded = Gadget.eager_load(:cables).first
1737
+ expect(loaded.cables).to eq([ cable ])
1738
+ end
1739
+ end
1740
+ end
1741
+
1742
+ context 'when a belongs_to lives inside an embedded document' do
1743
+ before(:all) do
1744
+ class Device
1745
+ include Mongoid::Document
1746
+ end
1747
+
1748
+ class Port
1749
+ include Mongoid::Document
1750
+
1751
+ embedded_in :computer
1752
+ belongs_to :device
1753
+ end
1754
+
1755
+ class Computer
1756
+ include Mongoid::Document
1757
+
1758
+ embeds_one :port
1759
+ end
1760
+ end
1761
+
1762
+ after(:all) do
1763
+ Mongoid.deregister_model(Computer)
1764
+ Object.send(:remove_const, :Computer)
1765
+ Mongoid.deregister_model(Port)
1766
+ Object.send(:remove_const, :Port)
1767
+ Mongoid.deregister_model(Device)
1768
+ Object.send(:remove_const, :Device)
1769
+ end
1770
+
1771
+ let!(:device) { Device.create! }
1772
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
1773
+
1774
+ it 'eager-loads it through the embedded document' do
1775
+ expect_query(1) do
1776
+ loaded = Computer.eager_load(port: :device).first
1777
+ expect(loaded.port.device).to eq(device)
1778
+ end
1779
+ end
1780
+
1781
+ it 'leaves the embedded document absent instead of synthesizing it' do
1782
+ without_port = Computer.create!
1783
+ loaded = expect_query(1) do
1784
+ Computer.eager_load(port: :device).to_a.index_by(&:id)
1785
+ end
1786
+ expect_no_queries do
1787
+ expect(loaded[without_port.id].port).to be_nil
1788
+ end
1789
+ end
1790
+ end
1791
+
1792
+ context 'when two embeds_one associations of the same class are eager-loaded' do
1793
+ before(:all) do
1794
+ class Device
1795
+ include Mongoid::Document
1796
+ end
1797
+
1798
+ class Port
1799
+ include Mongoid::Document
1800
+
1801
+ embedded_in :computer
1802
+ belongs_to :device
1803
+ end
1804
+
1805
+ class Computer
1806
+ include Mongoid::Document
1807
+
1808
+ embeds_one :left_port, class_name: 'Port'
1809
+ embeds_one :right_port, class_name: 'Port'
1810
+ end
1811
+ end
1812
+
1813
+ after(:all) do
1814
+ Mongoid.deregister_model(Computer)
1815
+ Object.send(:remove_const, :Computer)
1816
+ Mongoid.deregister_model(Port)
1817
+ Object.send(:remove_const, :Port)
1818
+ Mongoid.deregister_model(Device)
1819
+ Object.send(:remove_const, :Device)
1820
+ end
1821
+
1822
+ let!(:device1) { Device.create! }
1823
+ let!(:device2) { Device.create! }
1824
+ let!(:computer) do
1825
+ Computer.create!(
1826
+ left_port: Port.new(device: device1),
1827
+ right_port: Port.new(device: device2)
1828
+ )
1829
+ end
1830
+
1831
+ it 'eager-loads the belongs_to in both embedded documents' do
1832
+ expect_query(1) do
1833
+ loaded = Computer.eager_load(left_port: :device, right_port: :device).first
1834
+ expect(loaded.left_port.device).to eq(device1)
1835
+ expect(loaded.right_port.device).to eq(device2)
1836
+ end
1837
+ end
1838
+ end
1839
+
1840
+ context 'when a belongs_to lives inside a doubly embedded document' do
1841
+ before(:all) do
1842
+ class Device
1843
+ include Mongoid::Document
1844
+ end
1845
+
1846
+ class Port
1847
+ include Mongoid::Document
1848
+
1849
+ embedded_in :panel
1850
+ belongs_to :device
1851
+ end
1852
+
1853
+ class Panel
1854
+ include Mongoid::Document
1855
+
1856
+ embedded_in :computer
1857
+ embeds_one :port
1858
+ end
1859
+
1860
+ class Computer
1861
+ include Mongoid::Document
1862
+
1863
+ embeds_one :panel
1864
+ end
1865
+ end
1866
+
1867
+ after(:all) do
1868
+ Mongoid.deregister_model(Computer)
1869
+ Object.send(:remove_const, :Computer)
1870
+ Mongoid.deregister_model(Panel)
1871
+ Object.send(:remove_const, :Panel)
1872
+ Mongoid.deregister_model(Port)
1873
+ Object.send(:remove_const, :Port)
1874
+ Mongoid.deregister_model(Device)
1875
+ Object.send(:remove_const, :Device)
1876
+ end
1877
+
1878
+ let!(:device) { Device.create! }
1879
+ let!(:computer) { Computer.create!(panel: Panel.new(port: Port.new(device: device))) }
1880
+
1881
+ it 'eager-loads it through both embedded documents' do
1882
+ expect_query(1) do
1883
+ loaded = Computer.eager_load(panel: { port: :device }).first
1884
+ expect(loaded.panel.port.device).to eq(device)
1885
+ end
1886
+ end
1887
+ end
1888
+
1889
+ context 'when a belongs_to is defined only on a subclass of an embedded document' do
1890
+ before(:all) do
1891
+ class Device
1892
+ include Mongoid::Document
1893
+ end
1894
+
1895
+ class Port
1896
+ include Mongoid::Document
1897
+
1898
+ embedded_in :computer
1899
+ end
1900
+
1901
+ class UsbPort < Port
1902
+ belongs_to :device
1903
+ end
1904
+
1905
+ class Computer
1906
+ include Mongoid::Document
1907
+
1908
+ embeds_one :port
1909
+ end
1910
+ end
1911
+
1912
+ after(:all) do
1913
+ Mongoid.deregister_model(Computer)
1914
+ Object.send(:remove_const, :Computer)
1915
+ Mongoid.deregister_model(UsbPort)
1916
+ Object.send(:remove_const, :UsbPort)
1917
+ Mongoid.deregister_model(Port)
1918
+ Object.send(:remove_const, :Port)
1919
+ Mongoid.deregister_model(Device)
1920
+ Object.send(:remove_const, :Device)
1921
+ end
1922
+
1923
+ let!(:device) { Device.create! }
1924
+ let!(:computer) { Computer.create!(port: UsbPort.new(device: device)) }
1925
+
1926
+ it 'eager-loads it through the embedded subclass' do
1927
+ expect_query(1) do
1928
+ loaded = Computer.eager_load(port: :device).first
1929
+ expect(loaded.port.device).to eq(device)
1930
+ end
1931
+ end
1932
+ end
1933
+
1934
+ context 'when a belongs_to lives inside an embeds_many' do
1935
+ before(:all) do
1936
+ class Device
1937
+ include Mongoid::Document
1938
+ end
1939
+
1940
+ class Port
1941
+ include Mongoid::Document
1942
+
1943
+ embedded_in :computer
1944
+ belongs_to :device
1945
+ end
1946
+
1947
+ class Computer
1948
+ include Mongoid::Document
1949
+
1950
+ embeds_many :ports
1951
+ end
1952
+ end
1953
+
1954
+ after(:all) do
1955
+ Mongoid.deregister_model(Computer)
1956
+ Object.send(:remove_const, :Computer)
1957
+ Mongoid.deregister_model(Port)
1958
+ Object.send(:remove_const, :Port)
1959
+ Mongoid.deregister_model(Device)
1960
+ Object.send(:remove_const, :Device)
1961
+ end
1962
+
1963
+ let!(:device1) { Device.create! }
1964
+ let!(:device2) { Device.create! }
1965
+ let!(:computer) { Computer.create!(ports: [ Port.new(device: device1), Port.new(device: device2) ]) }
1966
+
1967
+ it 'eager-loads each embedded belongs_to without extra queries' do
1968
+ expect_query(1) do
1969
+ loaded = Computer.eager_load(ports: :device).first
1970
+ expect(loaded.ports.map(&:device)).to eq([ device1, device2 ])
1971
+ end
1972
+ end
1973
+ end
1974
+
1975
+ context 'when a has_and_belongs_to_many lives inside an embedded document' do
1976
+ before(:all) do
1977
+ class Device
1978
+ include Mongoid::Document
1979
+ end
1980
+
1981
+ class Port
1982
+ include Mongoid::Document
1983
+
1984
+ embedded_in :computer
1985
+ has_and_belongs_to_many :devices
1986
+ end
1987
+
1988
+ class Computer
1989
+ include Mongoid::Document
1990
+
1991
+ embeds_one :port
1992
+ end
1993
+ end
1994
+
1995
+ after(:all) do
1996
+ Mongoid.deregister_model(Computer)
1997
+ Object.send(:remove_const, :Computer)
1998
+ Mongoid.deregister_model(Port)
1999
+ Object.send(:remove_const, :Port)
2000
+ Mongoid.deregister_model(Device)
2001
+ Object.send(:remove_const, :Device)
2002
+ end
2003
+
2004
+ let!(:device1) { Device.create! }
2005
+ let!(:device2) { Device.create! }
2006
+ let!(:computer) { Computer.create!(port: Port.new(devices: [ device1, device2 ])) }
2007
+
2008
+ it 'eager-loads the devices referenced by the embedded port' do
2009
+ expect_query(1) do
2010
+ loaded = Computer.eager_load(port: :devices).first
2011
+ expect(loaded.port.devices).to eq([ device1, device2 ])
2012
+ end
2013
+ end
2014
+ end
2015
+
2016
+ context 'when an embedded belongs_to targets a superclass of the queried subclass' do
2017
+ before(:all) do
2018
+ class Device
2019
+ include Mongoid::Document
2020
+ end
2021
+
2022
+ class Computer < Device
2023
+ embeds_one :port
2024
+ end
2025
+
2026
+ class Port
2027
+ include Mongoid::Document
2028
+
2029
+ embedded_in :computer
2030
+ belongs_to :device
2031
+ end
2032
+ end
2033
+
2034
+ after(:all) do
2035
+ Mongoid.deregister_model(Port)
2036
+ Object.send(:remove_const, :Port)
2037
+ Mongoid.deregister_model(Computer)
2038
+ Object.send(:remove_const, :Computer)
2039
+ Mongoid.deregister_model(Device)
2040
+ Object.send(:remove_const, :Device)
2041
+ end
2042
+
2043
+ let!(:device) { Device.create! }
2044
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
2045
+
2046
+ it 'eager-loads the embedded belongs_to through the queried subclass' do
2047
+ expect_query(1) do
2048
+ loaded = Computer.eager_load(port: :device).first
2049
+ expect(loaded.port.device).to eq(device)
2050
+ end
2051
+ end
2052
+ end
2053
+
2054
+ context 'when an embedded association is nested under a referenced one' do
2055
+ before(:all) do
2056
+ class Device
2057
+ include Mongoid::Document
2058
+ end
2059
+
2060
+ class Port
2061
+ include Mongoid::Document
2062
+
2063
+ embedded_in :computer
2064
+ belongs_to :device
2065
+ end
2066
+
2067
+ class Computer
2068
+ include Mongoid::Document
2069
+
2070
+ embeds_one :port
2071
+ end
2072
+
2073
+ class Desk
2074
+ include Mongoid::Document
2075
+
2076
+ belongs_to :computer
2077
+ end
2078
+ end
2079
+
2080
+ after(:all) do
2081
+ Mongoid.deregister_model(Desk)
2082
+ Object.send(:remove_const, :Desk)
2083
+ Mongoid.deregister_model(Computer)
2084
+ Object.send(:remove_const, :Computer)
2085
+ Mongoid.deregister_model(Port)
2086
+ Object.send(:remove_const, :Port)
2087
+ Mongoid.deregister_model(Device)
2088
+ Object.send(:remove_const, :Device)
2089
+ end
2090
+
2091
+ let!(:device) { Device.create! }
2092
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
2093
+ let!(:desk) { Desk.create!(computer: computer) }
2094
+
2095
+ it 'eager-loads the embedded association together with its referenced child' do
2096
+ expect_query(1) do
2097
+ loaded = Desk.eager_load(computer: { port: :device }).first
2098
+ expect(loaded.computer.port.device).to eq(device)
2099
+ end
2100
+ end
2101
+ end
2102
+
2103
+ context 'when an embedded association is nested two levels under a referenced one' do
2104
+ before(:all) do
2105
+ class Device
2106
+ include Mongoid::Document
2107
+ end
2108
+
2109
+ class Pin
2110
+ include Mongoid::Document
2111
+
2112
+ embedded_in :port
2113
+ belongs_to :device
2114
+ end
2115
+
2116
+ class Port
2117
+ include Mongoid::Document
2118
+
2119
+ embedded_in :computer
2120
+ embeds_one :pin
2121
+ end
2122
+
2123
+ class Computer
2124
+ include Mongoid::Document
2125
+
2126
+ embeds_one :port
2127
+ end
2128
+
2129
+ class Desk
2130
+ include Mongoid::Document
2131
+
2132
+ belongs_to :computer
2133
+ end
2134
+ end
2135
+
2136
+ after(:all) do
2137
+ Mongoid.deregister_model(Desk)
2138
+ Object.send(:remove_const, :Desk)
2139
+ Mongoid.deregister_model(Computer)
2140
+ Object.send(:remove_const, :Computer)
2141
+ Mongoid.deregister_model(Port)
2142
+ Object.send(:remove_const, :Port)
2143
+ Mongoid.deregister_model(Pin)
2144
+ Object.send(:remove_const, :Pin)
2145
+ Mongoid.deregister_model(Device)
2146
+ Object.send(:remove_const, :Device)
2147
+ end
2148
+
2149
+ let!(:device) { Device.create! }
2150
+ let!(:computer) { Computer.create!(port: Port.new(pin: Pin.new(device: device))) }
2151
+ let!(:desk) { Desk.create!(computer: computer) }
2152
+
2153
+ it 'eager-loads the doubly embedded association together with its referenced child' do
2154
+ expect_query(1) do
2155
+ loaded = Desk.eager_load(computer: { port: { pin: :device } }).first
2156
+ expect(loaded.computer.port.pin.device).to eq(device)
2157
+ end
2158
+ end
2159
+ end
2160
+
2161
+ context 'when an embeds_many is nested inside another embeds_many under a referenced one' do
2162
+ before(:all) do
2163
+ class Device
2164
+ include Mongoid::Document
2165
+ end
2166
+
2167
+ class Port
2168
+ include Mongoid::Document
2169
+
2170
+ embedded_in :card
2171
+ belongs_to :device
2172
+ end
2173
+
2174
+ class Card
2175
+ include Mongoid::Document
2176
+
2177
+ embedded_in :computer
2178
+ embeds_many :ports
2179
+ end
2180
+
2181
+ class Computer
2182
+ include Mongoid::Document
2183
+
2184
+ embeds_many :cards
2185
+ end
2186
+
2187
+ class Desk
2188
+ include Mongoid::Document
2189
+
2190
+ belongs_to :computer
2191
+ end
2192
+ end
2193
+
2194
+ after(:all) do
2195
+ Mongoid.deregister_model(Desk)
2196
+ Object.send(:remove_const, :Desk)
2197
+ Mongoid.deregister_model(Computer)
2198
+ Object.send(:remove_const, :Computer)
2199
+ Mongoid.deregister_model(Card)
2200
+ Object.send(:remove_const, :Card)
2201
+ Mongoid.deregister_model(Port)
2202
+ Object.send(:remove_const, :Port)
2203
+ Mongoid.deregister_model(Device)
2204
+ Object.send(:remove_const, :Device)
2205
+ end
2206
+
2207
+ let!(:devices) { Array.new(4) { Device.create! } }
2208
+ let!(:computer) do
2209
+ Computer.create!(
2210
+ cards: [
2211
+ Card.new(ports: [ Port.new(device: devices[0]), Port.new(device: devices[1]) ]),
2212
+ Card.new(ports: [ Port.new(device: devices[2]), Port.new(device: devices[3]) ])
2213
+ ]
2214
+ )
2215
+ end
2216
+ let!(:desk) { Desk.create!(computer: computer) }
2217
+
2218
+ it 'eager-loads each deeply embedded belongs_to correlated to its own element' do
2219
+ expect_query(1) do
2220
+ loaded = Desk.eager_load(computer: { cards: { ports: :device } }).first
2221
+ got = loaded.computer.cards.flat_map { |card| card.ports.map(&:device) }
2222
+ expect(got).to eq(devices)
2223
+ end
2224
+ end
2225
+ end
2226
+
2227
+ context 'when a polymorphic belongs_to is eager-loaded' do
2228
+ before(:all) do
2229
+ class Printer
2230
+ include Mongoid::Document
2231
+ end
2232
+
2233
+ class Scanner
2234
+ include Mongoid::Document
2235
+ end
2236
+
2237
+ class Cartridge
2238
+ include Mongoid::Document
2239
+
2240
+ belongs_to :device, polymorphic: true
2241
+ end
2242
+ end
2243
+
2244
+ after(:all) do
2245
+ Mongoid.deregister_model(Cartridge)
2246
+ Object.send(:remove_const, :Cartridge)
2247
+ Mongoid.deregister_model(Scanner)
2248
+ Object.send(:remove_const, :Scanner)
2249
+ Mongoid.deregister_model(Printer)
2250
+ Object.send(:remove_const, :Printer)
2251
+ end
2252
+
2253
+ let!(:printer) { Printer.create! }
2254
+ let!(:scanner) { Scanner.create! }
2255
+ let!(:printer_cartridge) { Cartridge.create!(device: printer) }
2256
+ let!(:scanner_cartridge) { Cartridge.create!(device: scanner) }
2257
+
2258
+ it 'eager-loads polymorphic targets of different types in a single extra query' do
2259
+ expect_query(2) do
2260
+ loaded = Cartridge.eager_load(:device).to_a.index_by(&:id)
2261
+ expect(loaded[printer_cartridge.id].device).to eq(printer)
2262
+ expect(loaded[scanner_cartridge.id].device).to eq(scanner)
2263
+ end
2264
+ end
2265
+ end
2266
+
2267
+ context 'when a polymorphic target is stored in another database' do
2268
+ before(:all) do
2269
+ class LocalDevice
2270
+ include Mongoid::Document
2271
+ end
2272
+
2273
+ class RemoteDevice
2274
+ include Mongoid::Document
2275
+
2276
+ store_in database: 'secondary'
2277
+ end
2278
+
2279
+ class Accessory
2280
+ include Mongoid::Document
2281
+
2282
+ belongs_to :device, polymorphic: true
2283
+ end
2284
+ end
2285
+
2286
+ after(:all) do
2287
+ Mongoid.deregister_model(Accessory)
2288
+ Object.send(:remove_const, :Accessory)
2289
+ Mongoid.deregister_model(RemoteDevice)
2290
+ Object.send(:remove_const, :RemoteDevice)
2291
+ Mongoid.deregister_model(LocalDevice)
2292
+ Object.send(:remove_const, :LocalDevice)
2293
+ end
2294
+
2295
+ # RemoteDevice lives in its own database, which the global cleanup hook
2296
+ # (default database only) does not touch, so drop it here.
2297
+ after { RemoteDevice.collection.drop }
2298
+
2299
+ let!(:local_device) { LocalDevice.create! }
2300
+ let!(:remote_device) { RemoteDevice.create! }
2301
+ let!(:local_accessory) { Accessory.create!(device: local_device) }
2302
+ let!(:remote_accessory) { Accessory.create!(device: remote_device) }
2303
+
2304
+ it 'eager-loads the target stored in the other database' do
2305
+ # One query materializes the roots, one $facet fetches the same-database
2306
+ # target, and one direct query fetches the target in the other database.
2307
+ loaded = expect_query(3) do
2308
+ Accessory.eager_load(:device).to_a.index_by(&:id)
2309
+ end
2310
+
2311
+ expect_no_queries do
2312
+ expect(loaded[local_accessory.id].device).to eq(local_device)
2313
+ expect(loaded[remote_accessory.id].device).to eq(remote_device)
2314
+ end
2315
+ end
2316
+ end
2317
+
2318
+ context 'when a non-polymorphic target is stored in another database' do
2319
+ before(:all) do
2320
+ class RemoteServer
2321
+ include Mongoid::Document
2322
+
2323
+ store_in database: 'secondary'
2324
+ end
2325
+
2326
+ class Workstation
2327
+ include Mongoid::Document
2328
+
2329
+ belongs_to :server, class_name: 'RemoteServer', optional: true
2330
+ end
2331
+ end
2332
+
2333
+ after(:all) do
2334
+ Mongoid.deregister_model(Workstation)
2335
+ Object.send(:remove_const, :Workstation)
2336
+ Mongoid.deregister_model(RemoteServer)
2337
+ Object.send(:remove_const, :RemoteServer)
2338
+ end
2339
+
2340
+ # RemoteServer lives in its own database, which the global cleanup hook
2341
+ # (default database only) does not touch, so drop it here.
2342
+ after { RemoteServer.collection.drop }
2343
+
2344
+ let!(:server) { RemoteServer.create! }
2345
+ let!(:workstation) { Workstation.create!(server: server) }
2346
+
2347
+ it 'eager-loads the target stored in the other database' do
2348
+ # A $lookup cannot reach another database, so this falls back to
2349
+ # separate-query loading: one query materializes the roots and one fetches
2350
+ # the targets, instead of silently returning nothing.
2351
+ loaded = expect_query(2) do
2352
+ Workstation.eager_load(:server).to_a
2353
+ end
2354
+
2355
+ expect_no_queries do
2356
+ expect(loaded.first.server).to eq(server)
2357
+ end
2358
+ end
2359
+ end
2360
+
2361
+ context 'when a has_many of the same name is defined on more than one subclass' do
2362
+ before(:all) do
2363
+ class Supplier
2364
+ include Mongoid::Document
2365
+ end
2366
+
2367
+ class Cog
2368
+ include Mongoid::Document
2369
+
2370
+ belongs_to :machine, optional: true
2371
+ belongs_to :supplier, optional: true
2372
+ end
2373
+
2374
+ class Belt
2375
+ include Mongoid::Document
2376
+
2377
+ belongs_to :machine, optional: true
2378
+ belongs_to :supplier, optional: true
2379
+ end
2380
+
2381
+ class Machine
2382
+ include Mongoid::Document
2383
+ end
2384
+
2385
+ class Lathe < Machine
2386
+ has_many :widgets, class_name: 'Cog', inverse_of: :machine
2387
+ end
2388
+
2389
+ class Press < Machine
2390
+ has_many :widgets, class_name: 'Belt', inverse_of: :machine
2391
+ end
2392
+ end
2393
+
2394
+ after(:all) do
2395
+ Mongoid.deregister_model(Press)
2396
+ Object.send(:remove_const, :Press)
2397
+ Mongoid.deregister_model(Lathe)
2398
+ Object.send(:remove_const, :Lathe)
2399
+ Mongoid.deregister_model(Machine)
2400
+ Object.send(:remove_const, :Machine)
2401
+ Mongoid.deregister_model(Belt)
2402
+ Object.send(:remove_const, :Belt)
2403
+ Mongoid.deregister_model(Cog)
2404
+ Object.send(:remove_const, :Cog)
2405
+ Mongoid.deregister_model(Supplier)
2406
+ Object.send(:remove_const, :Supplier)
2407
+ end
2408
+
2409
+ let!(:cog_supplier) { Supplier.create! }
2410
+ let!(:belt_supplier) { Supplier.create! }
2411
+ let!(:lathe) { Lathe.create! }
2412
+ let!(:press) { Press.create! }
2413
+ let!(:cog) { Cog.create!(machine: lathe, supplier: cog_supplier) }
2414
+ let!(:belt) { Belt.create!(machine: press, supplier: belt_supplier) }
2415
+
2416
+ it 'loads each subclass association without one overwriting the other' do
2417
+ loaded = expect_query(1) do
2418
+ Machine.eager_load(:widgets).to_a.index_by(&:id)
2419
+ end
2420
+ expect_no_queries do
2421
+ expect(loaded[lathe.id].widgets).to eq([ cog ])
2422
+ expect(loaded[press.id].widgets).to eq([ belt ])
2423
+ end
2424
+ end
2425
+
2426
+ it 'eager-loads inclusions nested under each subclass association' do
2427
+ loaded = expect_query(1) do
2428
+ Machine.eager_load(widgets: :supplier).to_a.index_by(&:id)
2429
+ end
2430
+ expect_no_queries do
2431
+ expect(loaded[lathe.id].widgets.first.supplier).to eq(cog_supplier)
2432
+ expect(loaded[press.id].widgets.first.supplier).to eq(belt_supplier)
2433
+ end
2434
+ end
2435
+ end
2436
+
2437
+ context 'when a has_many targets a class sharing its collection with sibling subclasses' do
2438
+ before(:all) do
2439
+ class Part
2440
+ include Mongoid::Document
2441
+ end
2442
+
2443
+ class Chip < Part
2444
+ belongs_to :board, class_name: 'Board', optional: true
2445
+ end
2446
+
2447
+ class Cable < Part
2448
+ belongs_to :board, class_name: 'Board', optional: true
2449
+ end
2450
+
2451
+ class Board
2452
+ include Mongoid::Document
2453
+
2454
+ has_many :chips, class_name: 'Chip', inverse_of: :board
2455
+ end
2456
+ end
2457
+
2458
+ after(:all) do
2459
+ Mongoid.deregister_model(Board)
2460
+ Object.send(:remove_const, :Board)
2461
+ Mongoid.deregister_model(Cable)
2462
+ Object.send(:remove_const, :Cable)
2463
+ Mongoid.deregister_model(Chip)
2464
+ Object.send(:remove_const, :Chip)
2465
+ Mongoid.deregister_model(Part)
2466
+ Object.send(:remove_const, :Part)
2467
+ end
2468
+
2469
+ let!(:board) { Board.create! }
2470
+ let!(:chip) { Chip.create!(board: board) }
2471
+ let!(:cable) { Cable.create!(board: board) }
2472
+
2473
+ it 'eager-loads only the chips, not the sibling subclass' do
2474
+ expect_query(1) do
2475
+ loaded = Board.eager_load(:chips).first
2476
+ expect(loaded.chips.to_a).to eq([ chip ])
2477
+ end
2478
+ end
2479
+ end
2480
+
2481
+ context 'when a document is saved after eager loading' do
2482
+ context 'when the reference lives inside an embedded document' do
2483
+ before(:all) do
2484
+ class Device
2485
+ include Mongoid::Document
2486
+ end
2487
+
2488
+ class Port
2489
+ include Mongoid::Document
2490
+
2491
+ embedded_in :computer
2492
+ belongs_to :device
2493
+ end
2494
+
2495
+ class Computer
2496
+ include Mongoid::Document
2497
+
2498
+ embeds_many :ports
2499
+ end
2500
+ end
2501
+
2502
+ after(:all) do
2503
+ Object.send(:remove_const, :Computer)
2504
+ Object.send(:remove_const, :Port)
2505
+ Object.send(:remove_const, :Device)
2506
+ end
2507
+
2508
+ let!(:device) { Device.create! }
2509
+ let!(:computer) { Computer.create!(ports: [ Port.new(device: device) ]) }
2510
+
2511
+ it 'does not persist the eager-loaded association data' do
2512
+ loaded = Computer.eager_load(ports: :device).first
2513
+
2514
+ # Reassigning the collection makes the save rewrite the whole array,
2515
+ # serializing each already-hydrated port.
2516
+ loaded.ports = loaded.ports.to_a + [ Port.new(device: Device.create!) ]
2517
+ loaded.save!
2518
+
2519
+ # With the bug present, the persisted port comes back as:
2520
+ #
2521
+ # {
2522
+ # '_id' => ...,
2523
+ # 'device_id' => ...,
2524
+ # 'device' => [ { ...the whole device document... } ] # <-- the $lookup left this
2525
+ # }
2526
+ raw_ports = Computer.collection.find(_id: computer.id).first['ports']
2527
+ expect(raw_ports.first).to eq('_id' => computer.ports.first.id, 'device_id' => device.id)
2528
+ end
2529
+ end
2530
+
2531
+ context 'when the reference lives on a referenced document' do
2532
+ before(:all) do
2533
+ class Device
2534
+ include Mongoid::Document
2535
+ end
2536
+
2537
+ class Port
2538
+ include Mongoid::Document
2539
+
2540
+ belongs_to :computer
2541
+ belongs_to :device
2542
+ end
2543
+
2544
+ class Computer
2545
+ include Mongoid::Document
2546
+
2547
+ has_many :ports
2548
+ end
2549
+ end
2550
+
2551
+ after(:all) do
2552
+ Object.send(:remove_const, :Computer)
2553
+ Object.send(:remove_const, :Port)
2554
+ Object.send(:remove_const, :Device)
2555
+ end
2556
+
2557
+ let!(:device) { Device.create! }
2558
+ let!(:computer) { Computer.create!(ports: [ Port.new(device: device) ]) }
2559
+
2560
+ it 'does not persist the eager-loaded association data' do
2561
+ loaded = Computer.eager_load(ports: :device).first
2562
+ port = loaded.ports.first
2563
+
2564
+ # An upsert rewrites the whole document, so everything the port
2565
+ # carries gets serialized.
2566
+ port.upsert
2567
+
2568
+ # With the bug present, the persisted port comes back as:
2569
+ #
2570
+ # {
2571
+ # '_id' => ...,
2572
+ # 'computer_id' => ...,
2573
+ # 'device_id' => ...,
2574
+ # 'device' => [ { ...the whole device document... } ] # <-- the $lookup left this
2575
+ # }
2576
+ expect(Port.collection.find(_id: port.id).first)
2577
+ .to eq('_id' => port.id, 'computer_id' => computer.id, 'device_id' => device.id)
2578
+ end
2579
+ end
2580
+
2581
+ context 'when more than one subclass defines the association' do
2582
+ before(:all) do
2583
+ class Cog
2584
+ include Mongoid::Document
2585
+
2586
+ belongs_to :machine, optional: true
2587
+ end
2588
+
2589
+ class Belt
2590
+ include Mongoid::Document
2591
+
2592
+ belongs_to :machine, optional: true
2593
+ end
2594
+
2595
+ class Machine
2596
+ include Mongoid::Document
2597
+ end
2598
+
2599
+ class Lathe < Machine
2600
+ has_many :widgets, class_name: 'Cog', inverse_of: :machine
2601
+ end
2602
+
2603
+ class Press < Machine
2604
+ has_many :widgets, class_name: 'Belt', inverse_of: :machine
2605
+ end
2606
+ end
2607
+
2608
+ after(:all) do
2609
+ Object.send(:remove_const, :Press)
2610
+ Object.send(:remove_const, :Lathe)
2611
+ Object.send(:remove_const, :Machine)
2612
+ Object.send(:remove_const, :Belt)
2613
+ Object.send(:remove_const, :Cog)
2614
+ end
2615
+
2616
+ let!(:lathe) { Lathe.create! }
2617
+ let!(:cog) { Cog.create!(machine: lathe) }
2618
+
2619
+ it 'does not persist the eager-loaded association data' do
2620
+ loaded = Machine.eager_load(:widgets).first
2621
+
2622
+ # An upsert rewrites the whole document, so everything the lathe
2623
+ # carries gets serialized.
2624
+ loaded.upsert
2625
+
2626
+ # With the bug present, the persisted lathe comes back as:
2627
+ #
2628
+ # {
2629
+ # '_id' => ...,
2630
+ # '_type' => 'Lathe',
2631
+ # 'widgets' => [ { '_id' => ..., 'machine_id' => ... } ] # <-- the $lookup left this
2632
+ # }
2633
+ expect(Machine.collection.find(_id: lathe.id).first)
2634
+ .to eq('_id' => lathe.id, '_type' => 'Lathe')
2635
+ end
2636
+ end
2637
+ end
1417
2638
  end
1418
2639
 
1419
2640
  describe '#inclusions' do