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.
- checksums.yaml +4 -4
- data/lib/config/locales/en.yml +69 -0
- data/lib/mongoid/association/accessors.rb +27 -5
- data/lib/mongoid/association/depending.rb +14 -10
- data/lib/mongoid/association/eager.rb +9 -0
- data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
- data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
- data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
- data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
- data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
- data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
- data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
- data/lib/mongoid/association/eager_loadable.rb +65 -101
- data/lib/mongoid/association/embedded/batchable.rb +17 -1
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
- data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
- data/lib/mongoid/association/macros.rb +13 -1
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/nested/one.rb +6 -1
- data/lib/mongoid/association/options.rb +17 -0
- data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
- data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
- data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
- data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
- data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
- data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
- data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
- data/lib/mongoid/association/referenced/has_one.rb +1 -0
- data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
- data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
- data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
- data/lib/mongoid/association/referenced.rb +2 -0
- data/lib/mongoid/association/relatable.rb +58 -0
- data/lib/mongoid/association.rb +15 -0
- data/lib/mongoid/atomic/modifiers.rb +100 -16
- data/lib/mongoid/clients/factory.rb +9 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +170 -22
- data/lib/mongoid/config.rb +56 -5
- data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/includable.rb +35 -11
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
- data/lib/mongoid/deprecable.rb +45 -2
- data/lib/mongoid/document.rb +3 -0
- data/lib/mongoid/encryptable.rb +73 -0
- data/lib/mongoid/errors/config_redactor.rb +43 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
- data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
- data/lib/mongoid/errors/no_client_database.rb +1 -1
- data/lib/mongoid/errors/no_client_hosts.rb +1 -1
- data/lib/mongoid/errors/no_encryption_schema.rb +30 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +5 -0
- data/lib/mongoid/extensions/vector.rb +64 -0
- data/lib/mongoid/extensions.rb +1 -0
- data/lib/mongoid/field_readable.rb +69 -0
- data/lib/mongoid/fields.rb +7 -0
- data/lib/mongoid/indexable.rb +38 -26
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
- data/lib/mongoid/matcher/regex.rb +11 -12
- data/lib/mongoid/matcher/regexp_budget.rb +372 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistable/creatable.rb +120 -14
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistable.rb +15 -15
- data/lib/mongoid/persistence_context.rb +77 -0
- data/lib/mongoid/search_indexable.rb +69 -24
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +33 -16
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/touchable.rb +28 -1
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +4 -0
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +31 -2
- data/spec/integration/atomic/modifiers_spec.rb +283 -0
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +300 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/accessors_spec.rb +35 -0
- data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
- data/spec/mongoid/association/eager_spec.rb +2 -2
- data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
- data/spec/mongoid/association/fallback_spec.rb +425 -0
- data/spec/mongoid/association/macros_spec.rb +26 -0
- data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
- data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
- data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
- data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
- data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
- data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
- data/spec/mongoid/attributes/nested_spec.rb +278 -8
- data/spec/mongoid/clients/factory_spec.rb +36 -0
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +451 -0
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/includable_spec.rb +1221 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
- data/spec/mongoid/criteria_spec.rb +3 -0
- data/spec/mongoid/encryptable_spec.rb +115 -0
- data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
- data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
- data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
- data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
- data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
- data/spec/mongoid/extensions/vector_spec.rb +175 -0
- data/spec/mongoid/fields_spec.rb +8 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +321 -9
- data/spec/mongoid/tasks/database_spec.rb +31 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +242 -0
- data/spec/mongoid/touchable_spec_models.rb +47 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +253 -0
- data/spec/support/models/modifier_models.rb +65 -0
- 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
|