mongoid 9.1.0 → 9.1.1

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +49 -0
  3. data/lib/mongoid/association/accessors.rb +9 -1
  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 +57 -100
  14. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
  15. data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
  16. data/lib/mongoid/association/macros.rb +13 -1
  17. data/lib/mongoid/association/nested/many.rb +34 -5
  18. data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
  19. data/lib/mongoid/association/nested/one.rb +6 -1
  20. data/lib/mongoid/association/options.rb +17 -0
  21. data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
  22. data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
  23. data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
  24. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
  25. data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
  26. data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
  27. data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
  28. data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
  29. data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
  30. data/lib/mongoid/association/referenced/has_one.rb +1 -0
  31. data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
  32. data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
  33. data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
  34. data/lib/mongoid/association/referenced.rb +2 -0
  35. data/lib/mongoid/association/relatable.rb +40 -0
  36. data/lib/mongoid/association.rb +7 -0
  37. data/lib/mongoid/clients/factory.rb +7 -13
  38. data/lib/mongoid/collection_configurable.rb +8 -0
  39. data/lib/mongoid/config/defaults.rb +0 -1
  40. data/lib/mongoid/config/encryption.rb +36 -19
  41. data/lib/mongoid/config.rb +56 -5
  42. data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
  43. data/lib/mongoid/contextual/memory.rb +18 -7
  44. data/lib/mongoid/criteria/includable.rb +35 -11
  45. data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
  46. data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
  47. data/lib/mongoid/deprecable.rb +45 -2
  48. data/lib/mongoid/encryptable.rb +46 -0
  49. data/lib/mongoid/errors/config_redactor.rb +43 -0
  50. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
  51. data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
  52. data/lib/mongoid/errors/no_client_database.rb +1 -1
  53. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  54. data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
  55. data/lib/mongoid/errors/readonly_association.rb +29 -0
  56. data/lib/mongoid/errors.rb +4 -0
  57. data/lib/mongoid/extensions/vector.rb +64 -0
  58. data/lib/mongoid/extensions.rb +1 -0
  59. data/lib/mongoid/field_readable.rb +69 -0
  60. data/lib/mongoid/indexable.rb +38 -26
  61. data/lib/mongoid/matchable.rb +6 -1
  62. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
  63. data/lib/mongoid/matcher/regex.rb +11 -12
  64. data/lib/mongoid/matcher/regexp_budget.rb +372 -0
  65. data/lib/mongoid/matcher.rb +1 -0
  66. data/lib/mongoid/persistable/creatable.rb +1 -1
  67. data/lib/mongoid/persistable/updatable.rb +9 -11
  68. data/lib/mongoid/persistence_context.rb +35 -0
  69. data/lib/mongoid/search_indexable.rb +64 -23
  70. data/lib/mongoid/serializable.rb +12 -4
  71. data/lib/mongoid/tasks/database.rb +23 -9
  72. data/lib/mongoid/threaded.rb +36 -0
  73. data/lib/mongoid/timestamps/timeless.rb +84 -10
  74. data/lib/mongoid/traversable.rb +2 -2
  75. data/lib/mongoid/validatable/associated.rb +18 -2
  76. data/lib/mongoid/version.rb +1 -1
  77. data/spec/integration/app_spec.rb +7 -1
  78. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  79. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
  80. data/spec/integration/dots_and_dollars_spec.rb +12 -2
  81. data/spec/integration/encryption_spec.rb +151 -0
  82. data/spec/integration/matcher_operator_data/regex.yml +21 -0
  83. data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
  84. data/spec/integration/query_operator_guard_spec.rb +89 -0
  85. data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
  86. data/spec/mongoid/association/eager_spec.rb +2 -2
  87. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
  88. data/spec/mongoid/association/fallback_spec.rb +425 -0
  89. data/spec/mongoid/association/macros_spec.rb +26 -0
  90. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
  91. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
  92. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
  93. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
  94. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
  95. data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
  96. data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
  97. data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
  98. data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
  99. data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
  100. data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
  101. data/spec/mongoid/attributes/nested_spec.rb +249 -8
  102. data/spec/mongoid/config/defaults_spec.rb +33 -0
  103. data/spec/mongoid/config/encryption_spec.rb +228 -0
  104. data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
  105. data/spec/mongoid/contextual/memory_spec.rb +177 -0
  106. data/spec/mongoid/criteria/includable_spec.rb +875 -0
  107. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
  108. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
  109. data/spec/mongoid/criteria_spec.rb +3 -0
  110. data/spec/mongoid/encryptable_spec.rb +61 -0
  111. data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
  112. data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
  113. data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
  114. data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
  115. data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
  116. data/spec/mongoid/extensions/vector_spec.rb +175 -0
  117. data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
  118. data/spec/mongoid/search_indexable_spec.rb +295 -9
  119. data/spec/mongoid/tasks/database_spec.rb +18 -0
  120. data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
  121. data/spec/mongoid/touchable_spec.rb +25 -0
  122. data/spec/mongoid/touchable_spec_models.rb +10 -0
  123. data/spec/mongoid/validatable/associated_spec.rb +19 -0
  124. data/spec/spec_helper.rb +5 -1
  125. data/spec/support/crypt/models.rb +157 -0
  126. metadata +52 -2
@@ -1414,6 +1414,881 @@ describe Mongoid::Criteria::Includable do
1414
1414
  # 4.4 lookup does not support the lookup pipeline as it is currently written
1415
1415
  min_server_version '5.0'
1416
1416
  it_behaves_like 'eager loading', :eager_load
1417
+
1418
+ context 'when the inclusion list is empty' do
1419
+ let!(:band) { Band.create! }
1420
+
1421
+ it 'returns the documents without eager loading' do
1422
+ expect(Band.eager_load([]).first).to eq(band)
1423
+ end
1424
+ end
1425
+
1426
+ context 'when the association is defined on a subclass of the queried class' do
1427
+ before(:all) do
1428
+ class Machine
1429
+ include Mongoid::Document
1430
+ end
1431
+
1432
+ class Peripheral
1433
+ include Mongoid::Document
1434
+ end
1435
+
1436
+ class Webcam < Peripheral
1437
+ end
1438
+
1439
+ class Mouse < Peripheral
1440
+ belongs_to :machine
1441
+ end
1442
+ end
1443
+
1444
+ after(:all) do
1445
+ Object.send(:remove_const, :Mouse)
1446
+ Object.send(:remove_const, :Webcam)
1447
+ Object.send(:remove_const, :Peripheral)
1448
+ Object.send(:remove_const, :Machine)
1449
+ end
1450
+
1451
+ let!(:machine) { Machine.create! }
1452
+ let!(:webcam) { Webcam.create! }
1453
+ let!(:mouse) { Mouse.create!(machine: machine) }
1454
+
1455
+ it 'eager-loads it through the queried superclass' do
1456
+ expect_query(1) do
1457
+ loaded = Peripheral.eager_load(:machine).detect { |doc| doc.is_a?(Mouse) }
1458
+ expect(loaded.machine).to eq(machine)
1459
+ end
1460
+ end
1461
+ end
1462
+
1463
+ context 'when the association is defined on a subclass of an associated class' do
1464
+ before(:all) do
1465
+ class Brand
1466
+ include Mongoid::Document
1467
+ end
1468
+
1469
+ class Machine
1470
+ include Mongoid::Document
1471
+ end
1472
+
1473
+ class Laptop < Machine
1474
+ belongs_to :brand
1475
+ end
1476
+
1477
+ class Peripheral
1478
+ include Mongoid::Document
1479
+ end
1480
+
1481
+ class Mouse < Peripheral
1482
+ belongs_to :machine
1483
+ end
1484
+ end
1485
+
1486
+ after(:all) do
1487
+ Object.send(:remove_const, :Mouse)
1488
+ Object.send(:remove_const, :Peripheral)
1489
+ Object.send(:remove_const, :Laptop)
1490
+ Object.send(:remove_const, :Machine)
1491
+ Object.send(:remove_const, :Brand)
1492
+ end
1493
+
1494
+ let!(:brand) { Brand.create! }
1495
+ let!(:laptop) { Laptop.create!(brand: brand) }
1496
+ let!(:mouse) { Mouse.create!(machine: laptop) }
1497
+
1498
+ it 'eager-loads it through the parent association' do
1499
+ expect_query(1) do
1500
+ loaded = Peripheral.eager_load(machine: :brand).first
1501
+ expect(loaded.machine).to eq(laptop)
1502
+ expect(loaded.machine.brand).to eq(brand)
1503
+ end
1504
+ end
1505
+ end
1506
+
1507
+ context 'when a has_many is defined only on a subclass' do
1508
+ before(:all) do
1509
+ class Gadget
1510
+ include Mongoid::Document
1511
+ end
1512
+
1513
+ class Speaker < Gadget
1514
+ has_many :cables
1515
+ end
1516
+
1517
+ class Cable
1518
+ include Mongoid::Document
1519
+
1520
+ belongs_to :speaker
1521
+ end
1522
+ end
1523
+
1524
+ after(:all) do
1525
+ Object.send(:remove_const, :Cable)
1526
+ Object.send(:remove_const, :Speaker)
1527
+ Object.send(:remove_const, :Gadget)
1528
+ end
1529
+
1530
+ let!(:speaker) { Speaker.create! }
1531
+ let!(:cable) { Cable.create!(speaker: speaker) }
1532
+
1533
+ it 'eager-loads it through the queried superclass' do
1534
+ expect_query(1) do
1535
+ loaded = Gadget.eager_load(:cables).first
1536
+ expect(loaded.cables).to eq([ cable ])
1537
+ end
1538
+ end
1539
+ end
1540
+
1541
+ context 'when a has_one is defined only on a subclass' do
1542
+ before(:all) do
1543
+ class Gadget
1544
+ include Mongoid::Document
1545
+ end
1546
+
1547
+ class Speaker < Gadget
1548
+ has_one :cable
1549
+ end
1550
+
1551
+ class Cable
1552
+ include Mongoid::Document
1553
+
1554
+ belongs_to :speaker
1555
+ end
1556
+ end
1557
+
1558
+ after(:all) do
1559
+ Object.send(:remove_const, :Cable)
1560
+ Object.send(:remove_const, :Speaker)
1561
+ Object.send(:remove_const, :Gadget)
1562
+ end
1563
+
1564
+ let!(:speaker) { Speaker.create! }
1565
+ let!(:cable) { Cable.create!(speaker: speaker) }
1566
+
1567
+ it 'eager-loads it through the queried superclass' do
1568
+ expect_query(1) do
1569
+ loaded = Gadget.eager_load(:cable).first
1570
+ expect(loaded.cable).to eq(cable)
1571
+ end
1572
+ end
1573
+ end
1574
+
1575
+ context 'when a has_and_belongs_to_many is defined only on a subclass' do
1576
+ before(:all) do
1577
+ class Cable
1578
+ include Mongoid::Document
1579
+
1580
+ has_and_belongs_to_many :speakers
1581
+ end
1582
+
1583
+ class Gadget
1584
+ include Mongoid::Document
1585
+ end
1586
+
1587
+ class Speaker < Gadget
1588
+ has_and_belongs_to_many :cables
1589
+ end
1590
+ end
1591
+
1592
+ after(:all) do
1593
+ Object.send(:remove_const, :Speaker)
1594
+ Object.send(:remove_const, :Gadget)
1595
+ Object.send(:remove_const, :Cable)
1596
+ end
1597
+
1598
+ let!(:cable) { Cable.create! }
1599
+ let!(:speaker) { Speaker.create!(cables: [ cable ]) }
1600
+
1601
+ it 'eager-loads it through the queried superclass' do
1602
+ expect_query(1) do
1603
+ loaded = Gadget.eager_load(:cables).first
1604
+ expect(loaded.cables).to eq([ cable ])
1605
+ end
1606
+ end
1607
+ end
1608
+
1609
+ context 'when a belongs_to lives inside an embedded document' do
1610
+ before(:all) do
1611
+ class Device
1612
+ include Mongoid::Document
1613
+ end
1614
+
1615
+ class Port
1616
+ include Mongoid::Document
1617
+
1618
+ embedded_in :computer
1619
+ belongs_to :device
1620
+ end
1621
+
1622
+ class Computer
1623
+ include Mongoid::Document
1624
+
1625
+ embeds_one :port
1626
+ end
1627
+ end
1628
+
1629
+ after(:all) do
1630
+ Object.send(:remove_const, :Computer)
1631
+ Object.send(:remove_const, :Port)
1632
+ Object.send(:remove_const, :Device)
1633
+ end
1634
+
1635
+ let!(:device) { Device.create! }
1636
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
1637
+
1638
+ it 'eager-loads it through the embedded document' do
1639
+ expect_query(1) do
1640
+ loaded = Computer.eager_load(port: :device).first
1641
+ expect(loaded.port.device).to eq(device)
1642
+ end
1643
+ end
1644
+
1645
+ it 'leaves the embedded document absent instead of synthesizing it' do
1646
+ without_port = Computer.create!
1647
+ loaded = expect_query(1) do
1648
+ Computer.eager_load(port: :device).to_a.index_by(&:id)
1649
+ end
1650
+ expect_no_queries do
1651
+ expect(loaded[without_port.id].port).to be_nil
1652
+ end
1653
+ end
1654
+ end
1655
+
1656
+ context 'when two embeds_one associations of the same class are eager-loaded' do
1657
+ before(:all) do
1658
+ class Device
1659
+ include Mongoid::Document
1660
+ end
1661
+
1662
+ class Port
1663
+ include Mongoid::Document
1664
+
1665
+ embedded_in :computer
1666
+ belongs_to :device
1667
+ end
1668
+
1669
+ class Computer
1670
+ include Mongoid::Document
1671
+
1672
+ embeds_one :left_port, class_name: 'Port'
1673
+ embeds_one :right_port, class_name: 'Port'
1674
+ end
1675
+ end
1676
+
1677
+ after(:all) do
1678
+ Object.send(:remove_const, :Computer)
1679
+ Object.send(:remove_const, :Port)
1680
+ Object.send(:remove_const, :Device)
1681
+ end
1682
+
1683
+ let!(:device1) { Device.create! }
1684
+ let!(:device2) { Device.create! }
1685
+ let!(:computer) do
1686
+ Computer.create!(
1687
+ left_port: Port.new(device: device1),
1688
+ right_port: Port.new(device: device2)
1689
+ )
1690
+ end
1691
+
1692
+ it 'eager-loads the belongs_to in both embedded documents' do
1693
+ expect_query(1) do
1694
+ loaded = Computer.eager_load(left_port: :device, right_port: :device).first
1695
+ expect(loaded.left_port.device).to eq(device1)
1696
+ expect(loaded.right_port.device).to eq(device2)
1697
+ end
1698
+ end
1699
+ end
1700
+
1701
+ context 'when a belongs_to lives inside a doubly embedded document' do
1702
+ before(:all) do
1703
+ class Device
1704
+ include Mongoid::Document
1705
+ end
1706
+
1707
+ class Port
1708
+ include Mongoid::Document
1709
+
1710
+ embedded_in :panel
1711
+ belongs_to :device
1712
+ end
1713
+
1714
+ class Panel
1715
+ include Mongoid::Document
1716
+
1717
+ embedded_in :computer
1718
+ embeds_one :port
1719
+ end
1720
+
1721
+ class Computer
1722
+ include Mongoid::Document
1723
+
1724
+ embeds_one :panel
1725
+ end
1726
+ end
1727
+
1728
+ after(:all) do
1729
+ Object.send(:remove_const, :Computer)
1730
+ Object.send(:remove_const, :Panel)
1731
+ Object.send(:remove_const, :Port)
1732
+ Object.send(:remove_const, :Device)
1733
+ end
1734
+
1735
+ let!(:device) { Device.create! }
1736
+ let!(:computer) { Computer.create!(panel: Panel.new(port: Port.new(device: device))) }
1737
+
1738
+ it 'eager-loads it through both embedded documents' do
1739
+ expect_query(1) do
1740
+ loaded = Computer.eager_load(panel: { port: :device }).first
1741
+ expect(loaded.panel.port.device).to eq(device)
1742
+ end
1743
+ end
1744
+ end
1745
+
1746
+ context 'when a belongs_to is defined only on a subclass of an embedded document' do
1747
+ before(:all) do
1748
+ class Device
1749
+ include Mongoid::Document
1750
+ end
1751
+
1752
+ class Port
1753
+ include Mongoid::Document
1754
+
1755
+ embedded_in :computer
1756
+ end
1757
+
1758
+ class UsbPort < Port
1759
+ belongs_to :device
1760
+ end
1761
+
1762
+ class Computer
1763
+ include Mongoid::Document
1764
+
1765
+ embeds_one :port
1766
+ end
1767
+ end
1768
+
1769
+ after(:all) do
1770
+ Object.send(:remove_const, :Computer)
1771
+ Object.send(:remove_const, :UsbPort)
1772
+ Object.send(:remove_const, :Port)
1773
+ Object.send(:remove_const, :Device)
1774
+ end
1775
+
1776
+ let!(:device) { Device.create! }
1777
+ let!(:computer) { Computer.create!(port: UsbPort.new(device: device)) }
1778
+
1779
+ it 'eager-loads it through the embedded subclass' do
1780
+ expect_query(1) do
1781
+ loaded = Computer.eager_load(port: :device).first
1782
+ expect(loaded.port.device).to eq(device)
1783
+ end
1784
+ end
1785
+ end
1786
+
1787
+ context 'when a belongs_to lives inside an embeds_many' do
1788
+ before(:all) do
1789
+ class Device
1790
+ include Mongoid::Document
1791
+ end
1792
+
1793
+ class Port
1794
+ include Mongoid::Document
1795
+
1796
+ embedded_in :computer
1797
+ belongs_to :device
1798
+ end
1799
+
1800
+ class Computer
1801
+ include Mongoid::Document
1802
+
1803
+ embeds_many :ports
1804
+ end
1805
+ end
1806
+
1807
+ after(:all) do
1808
+ Object.send(:remove_const, :Computer)
1809
+ Object.send(:remove_const, :Port)
1810
+ Object.send(:remove_const, :Device)
1811
+ end
1812
+
1813
+ let!(:device1) { Device.create! }
1814
+ let!(:device2) { Device.create! }
1815
+ let!(:computer) { Computer.create!(ports: [ Port.new(device: device1), Port.new(device: device2) ]) }
1816
+
1817
+ it 'eager-loads each embedded belongs_to without extra queries' do
1818
+ expect_query(1) do
1819
+ loaded = Computer.eager_load(ports: :device).first
1820
+ expect(loaded.ports.map(&:device)).to eq([ device1, device2 ])
1821
+ end
1822
+ end
1823
+ end
1824
+
1825
+ context 'when a has_and_belongs_to_many lives inside an embedded document' do
1826
+ before(:all) do
1827
+ class Device
1828
+ include Mongoid::Document
1829
+ end
1830
+
1831
+ class Port
1832
+ include Mongoid::Document
1833
+
1834
+ embedded_in :computer
1835
+ has_and_belongs_to_many :devices
1836
+ end
1837
+
1838
+ class Computer
1839
+ include Mongoid::Document
1840
+
1841
+ embeds_one :port
1842
+ end
1843
+ end
1844
+
1845
+ after(:all) do
1846
+ Object.send(:remove_const, :Computer)
1847
+ Object.send(:remove_const, :Port)
1848
+ Object.send(:remove_const, :Device)
1849
+ end
1850
+
1851
+ let!(:device1) { Device.create! }
1852
+ let!(:device2) { Device.create! }
1853
+ let!(:computer) { Computer.create!(port: Port.new(devices: [ device1, device2 ])) }
1854
+
1855
+ it 'eager-loads the devices referenced by the embedded port' do
1856
+ expect_query(1) do
1857
+ loaded = Computer.eager_load(port: :devices).first
1858
+ expect(loaded.port.devices).to eq([ device1, device2 ])
1859
+ end
1860
+ end
1861
+ end
1862
+
1863
+ context 'when an embedded belongs_to targets a superclass of the queried subclass' do
1864
+ before(:all) do
1865
+ class Device
1866
+ include Mongoid::Document
1867
+ end
1868
+
1869
+ class Computer < Device
1870
+ embeds_one :port
1871
+ end
1872
+
1873
+ class Port
1874
+ include Mongoid::Document
1875
+
1876
+ embedded_in :computer
1877
+ belongs_to :device
1878
+ end
1879
+ end
1880
+
1881
+ after(:all) do
1882
+ Object.send(:remove_const, :Port)
1883
+ Object.send(:remove_const, :Computer)
1884
+ Object.send(:remove_const, :Device)
1885
+ end
1886
+
1887
+ let!(:device) { Device.create! }
1888
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
1889
+
1890
+ it 'eager-loads the embedded belongs_to through the queried subclass' do
1891
+ expect_query(1) do
1892
+ loaded = Computer.eager_load(port: :device).first
1893
+ expect(loaded.port.device).to eq(device)
1894
+ end
1895
+ end
1896
+ end
1897
+
1898
+ context 'when an embedded association is nested under a referenced one' do
1899
+ before(:all) do
1900
+ class Device
1901
+ include Mongoid::Document
1902
+ end
1903
+
1904
+ class Port
1905
+ include Mongoid::Document
1906
+
1907
+ embedded_in :computer
1908
+ belongs_to :device
1909
+ end
1910
+
1911
+ class Computer
1912
+ include Mongoid::Document
1913
+
1914
+ embeds_one :port
1915
+ end
1916
+
1917
+ class Desk
1918
+ include Mongoid::Document
1919
+
1920
+ belongs_to :computer
1921
+ end
1922
+ end
1923
+
1924
+ after(:all) do
1925
+ Object.send(:remove_const, :Desk)
1926
+ Object.send(:remove_const, :Computer)
1927
+ Object.send(:remove_const, :Port)
1928
+ Object.send(:remove_const, :Device)
1929
+ end
1930
+
1931
+ let!(:device) { Device.create! }
1932
+ let!(:computer) { Computer.create!(port: Port.new(device: device)) }
1933
+ let!(:desk) { Desk.create!(computer: computer) }
1934
+
1935
+ it 'eager-loads the embedded association together with its referenced child' do
1936
+ expect_query(1) do
1937
+ loaded = Desk.eager_load(computer: { port: :device }).first
1938
+ expect(loaded.computer.port.device).to eq(device)
1939
+ end
1940
+ end
1941
+ end
1942
+
1943
+ context 'when an embedded association is nested two levels under a referenced one' do
1944
+ before(:all) do
1945
+ class Device
1946
+ include Mongoid::Document
1947
+ end
1948
+
1949
+ class Pin
1950
+ include Mongoid::Document
1951
+
1952
+ embedded_in :port
1953
+ belongs_to :device
1954
+ end
1955
+
1956
+ class Port
1957
+ include Mongoid::Document
1958
+
1959
+ embedded_in :computer
1960
+ embeds_one :pin
1961
+ end
1962
+
1963
+ class Computer
1964
+ include Mongoid::Document
1965
+
1966
+ embeds_one :port
1967
+ end
1968
+
1969
+ class Desk
1970
+ include Mongoid::Document
1971
+
1972
+ belongs_to :computer
1973
+ end
1974
+ end
1975
+
1976
+ after(:all) do
1977
+ Object.send(:remove_const, :Desk)
1978
+ Object.send(:remove_const, :Computer)
1979
+ Object.send(:remove_const, :Port)
1980
+ Object.send(:remove_const, :Pin)
1981
+ Object.send(:remove_const, :Device)
1982
+ end
1983
+
1984
+ let!(:device) { Device.create! }
1985
+ let!(:computer) { Computer.create!(port: Port.new(pin: Pin.new(device: device))) }
1986
+ let!(:desk) { Desk.create!(computer: computer) }
1987
+
1988
+ it 'eager-loads the doubly embedded association together with its referenced child' do
1989
+ expect_query(1) do
1990
+ loaded = Desk.eager_load(computer: { port: { pin: :device } }).first
1991
+ expect(loaded.computer.port.pin.device).to eq(device)
1992
+ end
1993
+ end
1994
+ end
1995
+
1996
+ context 'when an embeds_many is nested inside another embeds_many under a referenced one' do
1997
+ before(:all) do
1998
+ class Device
1999
+ include Mongoid::Document
2000
+ end
2001
+
2002
+ class Port
2003
+ include Mongoid::Document
2004
+
2005
+ embedded_in :card
2006
+ belongs_to :device
2007
+ end
2008
+
2009
+ class Card
2010
+ include Mongoid::Document
2011
+
2012
+ embedded_in :computer
2013
+ embeds_many :ports
2014
+ end
2015
+
2016
+ class Computer
2017
+ include Mongoid::Document
2018
+
2019
+ embeds_many :cards
2020
+ end
2021
+
2022
+ class Desk
2023
+ include Mongoid::Document
2024
+
2025
+ belongs_to :computer
2026
+ end
2027
+ end
2028
+
2029
+ after(:all) do
2030
+ Object.send(:remove_const, :Desk)
2031
+ Object.send(:remove_const, :Computer)
2032
+ Object.send(:remove_const, :Card)
2033
+ Object.send(:remove_const, :Port)
2034
+ Object.send(:remove_const, :Device)
2035
+ end
2036
+
2037
+ let!(:devices) { Array.new(4) { Device.create! } }
2038
+ let!(:computer) do
2039
+ Computer.create!(
2040
+ cards: [
2041
+ Card.new(ports: [ Port.new(device: devices[0]), Port.new(device: devices[1]) ]),
2042
+ Card.new(ports: [ Port.new(device: devices[2]), Port.new(device: devices[3]) ])
2043
+ ]
2044
+ )
2045
+ end
2046
+ let!(:desk) { Desk.create!(computer: computer) }
2047
+
2048
+ it 'eager-loads each deeply embedded belongs_to correlated to its own element' do
2049
+ expect_query(1) do
2050
+ loaded = Desk.eager_load(computer: { cards: { ports: :device } }).first
2051
+ got = loaded.computer.cards.flat_map { |card| card.ports.map(&:device) }
2052
+ expect(got).to eq(devices)
2053
+ end
2054
+ end
2055
+ end
2056
+
2057
+ context 'when a polymorphic belongs_to is eager-loaded' do
2058
+ before(:all) do
2059
+ class Printer
2060
+ include Mongoid::Document
2061
+ end
2062
+
2063
+ class Scanner
2064
+ include Mongoid::Document
2065
+ end
2066
+
2067
+ class Cartridge
2068
+ include Mongoid::Document
2069
+
2070
+ belongs_to :device, polymorphic: true
2071
+ end
2072
+ end
2073
+
2074
+ after(:all) do
2075
+ Object.send(:remove_const, :Cartridge)
2076
+ Object.send(:remove_const, :Scanner)
2077
+ Object.send(:remove_const, :Printer)
2078
+ end
2079
+
2080
+ let!(:printer) { Printer.create! }
2081
+ let!(:scanner) { Scanner.create! }
2082
+ let!(:printer_cartridge) { Cartridge.create!(device: printer) }
2083
+ let!(:scanner_cartridge) { Cartridge.create!(device: scanner) }
2084
+
2085
+ it 'eager-loads polymorphic targets of different types in a single extra query' do
2086
+ expect_query(2) do
2087
+ loaded = Cartridge.eager_load(:device).to_a.index_by(&:id)
2088
+ expect(loaded[printer_cartridge.id].device).to eq(printer)
2089
+ expect(loaded[scanner_cartridge.id].device).to eq(scanner)
2090
+ end
2091
+ end
2092
+ end
2093
+
2094
+ context 'when a polymorphic target is stored in another database' do
2095
+ before(:all) do
2096
+ class LocalDevice
2097
+ include Mongoid::Document
2098
+ end
2099
+
2100
+ class RemoteDevice
2101
+ include Mongoid::Document
2102
+
2103
+ store_in database: 'secondary'
2104
+ end
2105
+
2106
+ class Accessory
2107
+ include Mongoid::Document
2108
+
2109
+ belongs_to :device, polymorphic: true
2110
+ end
2111
+ end
2112
+
2113
+ after(:all) do
2114
+ Object.send(:remove_const, :Accessory)
2115
+ Object.send(:remove_const, :RemoteDevice)
2116
+ Object.send(:remove_const, :LocalDevice)
2117
+ end
2118
+
2119
+ # RemoteDevice lives in its own database, which the global cleanup hook
2120
+ # (default database only) does not touch, so drop it here.
2121
+ after { RemoteDevice.collection.drop }
2122
+
2123
+ let!(:local_device) { LocalDevice.create! }
2124
+ let!(:remote_device) { RemoteDevice.create! }
2125
+ let!(:local_accessory) { Accessory.create!(device: local_device) }
2126
+ let!(:remote_accessory) { Accessory.create!(device: remote_device) }
2127
+
2128
+ it 'eager-loads the target stored in the other database' do
2129
+ # One query materializes the roots, one $facet fetches the same-database
2130
+ # target, and one direct query fetches the target in the other database.
2131
+ loaded = expect_query(3) do
2132
+ Accessory.eager_load(:device).to_a.index_by(&:id)
2133
+ end
2134
+
2135
+ expect_no_queries do
2136
+ expect(loaded[local_accessory.id].device).to eq(local_device)
2137
+ expect(loaded[remote_accessory.id].device).to eq(remote_device)
2138
+ end
2139
+ end
2140
+ end
2141
+
2142
+ context 'when a non-polymorphic target is stored in another database' do
2143
+ before(:all) do
2144
+ class RemoteServer
2145
+ include Mongoid::Document
2146
+
2147
+ store_in database: 'secondary'
2148
+ end
2149
+
2150
+ class Workstation
2151
+ include Mongoid::Document
2152
+
2153
+ belongs_to :server, class_name: 'RemoteServer', optional: true
2154
+ end
2155
+ end
2156
+
2157
+ after(:all) do
2158
+ Object.send(:remove_const, :Workstation)
2159
+ Object.send(:remove_const, :RemoteServer)
2160
+ end
2161
+
2162
+ # RemoteServer lives in its own database, which the global cleanup hook
2163
+ # (default database only) does not touch, so drop it here.
2164
+ after { RemoteServer.collection.drop }
2165
+
2166
+ let!(:server) { RemoteServer.create! }
2167
+ let!(:workstation) { Workstation.create!(server: server) }
2168
+
2169
+ it 'eager-loads the target stored in the other database' do
2170
+ # A $lookup cannot reach another database, so this falls back to
2171
+ # separate-query loading: one query materializes the roots and one fetches
2172
+ # the targets, instead of silently returning nothing.
2173
+ loaded = expect_query(2) do
2174
+ Workstation.eager_load(:server).to_a
2175
+ end
2176
+
2177
+ expect_no_queries do
2178
+ expect(loaded.first.server).to eq(server)
2179
+ end
2180
+ end
2181
+ end
2182
+
2183
+ context 'when a has_many of the same name is defined on more than one subclass' do
2184
+ before(:all) do
2185
+ class Supplier
2186
+ include Mongoid::Document
2187
+ end
2188
+
2189
+ class Cog
2190
+ include Mongoid::Document
2191
+
2192
+ belongs_to :machine, optional: true
2193
+ belongs_to :supplier, optional: true
2194
+ end
2195
+
2196
+ class Belt
2197
+ include Mongoid::Document
2198
+
2199
+ belongs_to :machine, optional: true
2200
+ belongs_to :supplier, optional: true
2201
+ end
2202
+
2203
+ class Machine
2204
+ include Mongoid::Document
2205
+ end
2206
+
2207
+ class Lathe < Machine
2208
+ has_many :widgets, class_name: 'Cog', inverse_of: :machine
2209
+ end
2210
+
2211
+ class Press < Machine
2212
+ has_many :widgets, class_name: 'Belt', inverse_of: :machine
2213
+ end
2214
+ end
2215
+
2216
+ after(:all) do
2217
+ Object.send(:remove_const, :Press)
2218
+ Object.send(:remove_const, :Lathe)
2219
+ Object.send(:remove_const, :Machine)
2220
+ Object.send(:remove_const, :Belt)
2221
+ Object.send(:remove_const, :Cog)
2222
+ Object.send(:remove_const, :Supplier)
2223
+ end
2224
+
2225
+ let!(:cog_supplier) { Supplier.create! }
2226
+ let!(:belt_supplier) { Supplier.create! }
2227
+ let!(:lathe) { Lathe.create! }
2228
+ let!(:press) { Press.create! }
2229
+ let!(:cog) { Cog.create!(machine: lathe, supplier: cog_supplier) }
2230
+ let!(:belt) { Belt.create!(machine: press, supplier: belt_supplier) }
2231
+
2232
+ it 'loads each subclass association without one overwriting the other' do
2233
+ loaded = expect_query(1) do
2234
+ Machine.eager_load(:widgets).to_a.index_by(&:id)
2235
+ end
2236
+ expect_no_queries do
2237
+ expect(loaded[lathe.id].widgets).to eq([ cog ])
2238
+ expect(loaded[press.id].widgets).to eq([ belt ])
2239
+ end
2240
+ end
2241
+
2242
+ it 'eager-loads inclusions nested under each subclass association' do
2243
+ loaded = expect_query(1) do
2244
+ Machine.eager_load(widgets: :supplier).to_a.index_by(&:id)
2245
+ end
2246
+ expect_no_queries do
2247
+ expect(loaded[lathe.id].widgets.first.supplier).to eq(cog_supplier)
2248
+ expect(loaded[press.id].widgets.first.supplier).to eq(belt_supplier)
2249
+ end
2250
+ end
2251
+ end
2252
+
2253
+ context 'when a has_many targets a class sharing its collection with sibling subclasses' do
2254
+ before(:all) do
2255
+ class Part
2256
+ include Mongoid::Document
2257
+ end
2258
+
2259
+ class Chip < Part
2260
+ belongs_to :board, class_name: 'Board', optional: true
2261
+ end
2262
+
2263
+ class Cable < Part
2264
+ belongs_to :board, class_name: 'Board', optional: true
2265
+ end
2266
+
2267
+ class Board
2268
+ include Mongoid::Document
2269
+
2270
+ has_many :chips, class_name: 'Chip', inverse_of: :board
2271
+ end
2272
+ end
2273
+
2274
+ after(:all) do
2275
+ Object.send(:remove_const, :Board)
2276
+ Object.send(:remove_const, :Cable)
2277
+ Object.send(:remove_const, :Chip)
2278
+ Object.send(:remove_const, :Part)
2279
+ end
2280
+
2281
+ let!(:board) { Board.create! }
2282
+ let!(:chip) { Chip.create!(board: board) }
2283
+ let!(:cable) { Cable.create!(board: board) }
2284
+
2285
+ it 'eager-loads only the chips, not the sibling subclass' do
2286
+ expect_query(1) do
2287
+ loaded = Board.eager_load(:chips).first
2288
+ expect(loaded.chips.to_a).to eq([ chip ])
2289
+ end
2290
+ end
2291
+ end
1417
2292
  end
1418
2293
 
1419
2294
  describe '#inclusions' do