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
|
@@ -1400,6 +1400,46 @@ describe Mongoid::Touchable do
|
|
|
1400
1400
|
end
|
|
1401
1401
|
end
|
|
1402
1402
|
|
|
1403
|
+
describe 'the parent chain touch paths' do
|
|
1404
|
+
context 'on a root document' do
|
|
1405
|
+
it 'contains the root updated_at path' do
|
|
1406
|
+
expect(TouchableParent.new._touchable_chain_paths).to eq(%w[updated_at])
|
|
1407
|
+
end
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
context 'on an embedded document with a touchable parent' do
|
|
1411
|
+
let(:parent) do
|
|
1412
|
+
parent = TouchableParent.create!
|
|
1413
|
+
parent.child = TouchableChild.create!(parent: parent)
|
|
1414
|
+
parent
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1417
|
+
it 'contains the paths of each node in the chain' do
|
|
1418
|
+
expect(parent.child._touchable_chain_paths)
|
|
1419
|
+
.to eq(%w[child.updated_at updated_at])
|
|
1420
|
+
end
|
|
1421
|
+
end
|
|
1422
|
+
|
|
1423
|
+
context 'with a custom touch field' do
|
|
1424
|
+
it 'contains the custom field path of the anchor document' do
|
|
1425
|
+
building = TouchableSpec::Embedded::Building.create!
|
|
1426
|
+
|
|
1427
|
+
expect(building._touchable_chain_paths(:last_used_at))
|
|
1428
|
+
.to eq(%w[updated_at last_used_at])
|
|
1429
|
+
end
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
context 'when the models use Timestamps::Short' do
|
|
1433
|
+
it 'resolves the aliased timestamp fields' do
|
|
1434
|
+
building = TouchableSpec::Short::Building.create!
|
|
1435
|
+
building.floors.create!
|
|
1436
|
+
|
|
1437
|
+
expect(building.reload.floors.first._touchable_chain_paths)
|
|
1438
|
+
.to eq(%w[floors.0.u_at u_at])
|
|
1439
|
+
end
|
|
1440
|
+
end
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1403
1443
|
describe 'touch merged with embedded insert' do
|
|
1404
1444
|
context 'when pushing an embedded document with touch: true' do
|
|
1405
1445
|
let(:building) do
|
|
@@ -1424,6 +1464,21 @@ describe Mongoid::Touchable do
|
|
|
1424
1464
|
end
|
|
1425
1465
|
end
|
|
1426
1466
|
|
|
1467
|
+
it 'sends the merged touch updates rewritten to the positional operator' do
|
|
1468
|
+
subscriber = Mrss::EventSubscriber.new
|
|
1469
|
+
Mongoid.client(:default).subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
1470
|
+
begin
|
|
1471
|
+
floor.sofas.push(TouchableSpec::Embedded::Sofa.new)
|
|
1472
|
+
ensure
|
|
1473
|
+
Mongoid.client(:default).unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
command = subscriber.single_command_started_event('update').command
|
|
1477
|
+
sets = command['updates'].first['u']['$set']
|
|
1478
|
+
|
|
1479
|
+
expect(sets.keys).to contain_exactly('floors.$.updated_at', 'updated_at')
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1427
1482
|
it 'updates updated_at on the parent after push' do
|
|
1428
1483
|
original_floor_updated_at = floor.updated_at
|
|
1429
1484
|
original_building_updated_at = building.updated_at
|
|
@@ -1511,5 +1566,192 @@ describe Mongoid::Touchable do
|
|
|
1511
1566
|
expect(building.updated_at).to eq(original_building_updated_at)
|
|
1512
1567
|
end
|
|
1513
1568
|
end
|
|
1569
|
+
|
|
1570
|
+
context 'when creating an embeds_one document with touch: true (regression for #6128)' do
|
|
1571
|
+
let(:building) do
|
|
1572
|
+
TouchableSpec::Embedded::Building.create!(title: 'Tower')
|
|
1573
|
+
end
|
|
1574
|
+
|
|
1575
|
+
it 'persists the embedded document fields after create_<assoc>' do
|
|
1576
|
+
building.create_lobby(name: 'Main Lobby')
|
|
1577
|
+
|
|
1578
|
+
reloaded = TouchableSpec::Embedded::Building.find(building.id)
|
|
1579
|
+
expect(reloaded.lobby).not_to be_nil
|
|
1580
|
+
expect(reloaded.lobby.name).to eq('Main Lobby')
|
|
1581
|
+
end
|
|
1582
|
+
|
|
1583
|
+
it 'updates the parent updated_at after create_<assoc>' do
|
|
1584
|
+
original_updated_at = building.updated_at
|
|
1585
|
+
|
|
1586
|
+
Timecop.travel(Time.now + 10) do
|
|
1587
|
+
building.create_lobby(name: 'Main Lobby')
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
building.reload
|
|
1591
|
+
expect(building.updated_at).to be > original_updated_at
|
|
1592
|
+
end
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
context 'when an existing child of the same array has a pending touch' do
|
|
1596
|
+
let(:building) do
|
|
1597
|
+
TouchableSpec::Embedded::Building.create!(title: 'Tower')
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
before do
|
|
1601
|
+
floor = building.floors.create!(level: 1)
|
|
1602
|
+
# Leave a pending touch on the existing sibling so merging it into
|
|
1603
|
+
# the insert would target a path inside the pushed array.
|
|
1604
|
+
floor.updated_at = Time.now + 60
|
|
1605
|
+
end
|
|
1606
|
+
|
|
1607
|
+
it 'does not raise a conflict error when creating a sibling' do
|
|
1608
|
+
expect { building.floors.create!(level: 2) }.not_to raise_error
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
it 'persists the new sibling' do
|
|
1612
|
+
building.floors.create!(level: 2)
|
|
1613
|
+
expect(building.reload.floors.length).to eq(2)
|
|
1614
|
+
end
|
|
1615
|
+
|
|
1616
|
+
it 'touches the parent' do
|
|
1617
|
+
building.floors.create!(level: 2)
|
|
1618
|
+
building.reload
|
|
1619
|
+
expect(building.updated_at).to be_within(5).of(Time.now)
|
|
1620
|
+
end
|
|
1621
|
+
end
|
|
1622
|
+
|
|
1623
|
+
context 'when a sibling in a parent array has a pending touch' do
|
|
1624
|
+
let(:building) do
|
|
1625
|
+
TouchableSpec::Embedded::Building.create!(title: 'Tower')
|
|
1626
|
+
end
|
|
1627
|
+
|
|
1628
|
+
let(:first_floor) do
|
|
1629
|
+
building.floors.create!(level: 1)
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
let(:second_floor) do
|
|
1633
|
+
building.floors.create!(level: 2)
|
|
1634
|
+
end
|
|
1635
|
+
|
|
1636
|
+
let(:pending_touch_time) do
|
|
1637
|
+
Time.now + 60
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
before do
|
|
1641
|
+
first_floor
|
|
1642
|
+
# Leave a pending touch on the other floor, at a different index of
|
|
1643
|
+
# the same array. Rewriting both that touch and the insert's own
|
|
1644
|
+
# touch to the positional operator collapses them onto one path and
|
|
1645
|
+
# touches the wrong floor.
|
|
1646
|
+
second_floor.updated_at = pending_touch_time
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
it 'does not raise when creating a grandchild under the other floor' do
|
|
1650
|
+
expect { first_floor.sofas.create! }.not_to raise_error
|
|
1651
|
+
end
|
|
1652
|
+
|
|
1653
|
+
it 'touches the floor the grandchild was inserted under' do
|
|
1654
|
+
first_floor.sofas.create!
|
|
1655
|
+
building.reload
|
|
1656
|
+
expect(building.floors[0].updated_at).to be_within(5).of(Time.now)
|
|
1657
|
+
end
|
|
1658
|
+
|
|
1659
|
+
it 'preserves the pending touch on the sibling floor' do
|
|
1660
|
+
first_floor.sofas.create!
|
|
1661
|
+
building.reload
|
|
1662
|
+
expect(building.floors[1].updated_at.to_f)
|
|
1663
|
+
.to be_within(1).of(pending_touch_time.to_f)
|
|
1664
|
+
end
|
|
1665
|
+
end
|
|
1666
|
+
|
|
1667
|
+
context 'when the parent touch option names a custom field' do
|
|
1668
|
+
let(:building) do
|
|
1669
|
+
TouchableSpec::Embedded::Building.create!(title: 'Tower')
|
|
1670
|
+
end
|
|
1671
|
+
|
|
1672
|
+
it 'writes the custom touch field when creating an embedded child' do
|
|
1673
|
+
# Materialize the parent before the freeze so its updated_at differs
|
|
1674
|
+
# from the touch time, forcing the touch updates to merge into the
|
|
1675
|
+
# child's insert.
|
|
1676
|
+
building
|
|
1677
|
+
Timecop.freeze(Time.utc(2026, 1, 2, 3, 4, 5)) do
|
|
1678
|
+
building.waiting_rooms.create!
|
|
1679
|
+
building.reload
|
|
1680
|
+
expect(building.last_used_at).not_to be_nil
|
|
1681
|
+
expect(building.last_used_at.to_f)
|
|
1682
|
+
.to be_within(1).of(Time.utc(2026, 1, 2, 3, 4, 5).to_f)
|
|
1683
|
+
end
|
|
1684
|
+
end
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
context 'when the models use Timestamps::Short' do
|
|
1688
|
+
let(:building) do
|
|
1689
|
+
TouchableSpec::Short::Building.create!
|
|
1690
|
+
end
|
|
1691
|
+
|
|
1692
|
+
let(:floor) do
|
|
1693
|
+
building.floors.create!
|
|
1694
|
+
end
|
|
1695
|
+
|
|
1696
|
+
before do
|
|
1697
|
+
building
|
|
1698
|
+
floor
|
|
1699
|
+
end
|
|
1700
|
+
|
|
1701
|
+
it 'merges the touch updates into the insert round-trip' do
|
|
1702
|
+
expect_query(1) do
|
|
1703
|
+
floor.gadgets.push(TouchableSpec::Short::Gadget.new)
|
|
1704
|
+
end
|
|
1705
|
+
end
|
|
1706
|
+
|
|
1707
|
+
it 'touches the parent chain through the aliased timestamp fields' do
|
|
1708
|
+
original_building_updated_at = building.u_at
|
|
1709
|
+
original_floor_updated_at = floor.u_at
|
|
1710
|
+
|
|
1711
|
+
Timecop.travel(Time.now + 10) do
|
|
1712
|
+
floor.gadgets.push(TouchableSpec::Short::Gadget.new)
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
building.reload
|
|
1716
|
+
expect(building.u_at).to be > original_building_updated_at
|
|
1717
|
+
expect(building.floors.first.u_at).to be > original_floor_updated_at
|
|
1718
|
+
end
|
|
1719
|
+
end
|
|
1720
|
+
|
|
1721
|
+
context 'when a sibling has a pending touch and the touch callback does not run' do
|
|
1722
|
+
let(:building) do
|
|
1723
|
+
TouchableSpec::Embedded::Building.create!(title: 'Tower')
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
let(:first_floor) do
|
|
1727
|
+
building.floors.create!(level: 1)
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
let(:second_floor) do
|
|
1731
|
+
building.floors.create!(level: 2)
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
let(:pending_touch_time) do
|
|
1735
|
+
Time.now + 60
|
|
1736
|
+
end
|
|
1737
|
+
|
|
1738
|
+
before do
|
|
1739
|
+
first_floor
|
|
1740
|
+
# Leave a pending touch on the other floor: rewriting it and this
|
|
1741
|
+
# chain's own touch would collapse both onto the same positional
|
|
1742
|
+
# path, so the touch updates cannot be merged into the insert.
|
|
1743
|
+
second_floor.updated_at = pending_touch_time
|
|
1744
|
+
end
|
|
1745
|
+
|
|
1746
|
+
it 'persists the deferred touches at insert time' do
|
|
1747
|
+
Mongoid::Touchable.suppress_touch_callbacks(TouchableSpec::Embedded::Sofa.name) do
|
|
1748
|
+
first_floor.sofas.push(TouchableSpec::Embedded::Sofa.new)
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
building.reload
|
|
1752
|
+
expect(building.floors[1].updated_at.to_f)
|
|
1753
|
+
.to be_within(1).of(pending_touch_time.to_f)
|
|
1754
|
+
end
|
|
1755
|
+
end
|
|
1514
1756
|
end
|
|
1515
1757
|
end
|
|
@@ -7,9 +7,21 @@ module TouchableSpec
|
|
|
7
7
|
include Mongoid::Timestamps
|
|
8
8
|
|
|
9
9
|
field :title, type: String
|
|
10
|
+
field :last_used_at, type: Time
|
|
10
11
|
|
|
11
12
|
embeds_many :entrances, class_name: 'TouchableSpec::Embedded::Entrance'
|
|
12
13
|
embeds_many :floors, class_name: 'TouchableSpec::Embedded::Floor'
|
|
14
|
+
embeds_one :lobby, class_name: 'TouchableSpec::Embedded::Lobby'
|
|
15
|
+
embeds_many :waiting_rooms, class_name: 'TouchableSpec::Embedded::WaitingRoom'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Lobby
|
|
19
|
+
include Mongoid::Document
|
|
20
|
+
include Mongoid::Timestamps
|
|
21
|
+
|
|
22
|
+
field :name, type: String
|
|
23
|
+
|
|
24
|
+
embedded_in :building, touch: true, class_name: 'TouchableSpec::Embedded::Building'
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
class Entrance
|
|
@@ -65,6 +77,41 @@ module TouchableSpec
|
|
|
65
77
|
|
|
66
78
|
embedded_in :floor, touch: true, class_name: 'TouchableSpec::Embedded::Floor'
|
|
67
79
|
end
|
|
80
|
+
|
|
81
|
+
class WaitingRoom
|
|
82
|
+
include Mongoid::Document
|
|
83
|
+
include Mongoid::Timestamps
|
|
84
|
+
|
|
85
|
+
field :name, type: String
|
|
86
|
+
|
|
87
|
+
embedded_in :building, touch: :last_used_at,
|
|
88
|
+
class_name: 'TouchableSpec::Embedded::Building'
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module Short
|
|
93
|
+
class Building
|
|
94
|
+
include Mongoid::Document
|
|
95
|
+
include Mongoid::Timestamps::Short
|
|
96
|
+
|
|
97
|
+
embeds_many :floors, class_name: 'TouchableSpec::Short::Floor'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class Floor
|
|
101
|
+
include Mongoid::Document
|
|
102
|
+
include Mongoid::Timestamps::Short
|
|
103
|
+
|
|
104
|
+
embedded_in :building, touch: true, class_name: 'TouchableSpec::Short::Building'
|
|
105
|
+
|
|
106
|
+
embeds_many :gadgets, class_name: 'TouchableSpec::Short::Gadget'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class Gadget
|
|
110
|
+
include Mongoid::Document
|
|
111
|
+
include Mongoid::Timestamps::Short
|
|
112
|
+
|
|
113
|
+
embedded_in :floor, touch: true, class_name: 'TouchableSpec::Short::Floor'
|
|
114
|
+
end
|
|
68
115
|
end
|
|
69
116
|
|
|
70
117
|
module Referenced
|
|
@@ -82,6 +82,25 @@ describe Mongoid::Validatable::AssociatedValidator do
|
|
|
82
82
|
expect(user).to be_valid
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
|
+
|
|
86
|
+
context 'when a child document has been destroyed' do
|
|
87
|
+
let(:user) do
|
|
88
|
+
User.new(name: 'test')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
let(:description) do
|
|
92
|
+
Description.new
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
before do
|
|
96
|
+
user.descriptions << description
|
|
97
|
+
description.destroyed = true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'does not run validation on the destroyed child' do
|
|
101
|
+
expect(user).to be_valid
|
|
102
|
+
end
|
|
103
|
+
end
|
|
85
104
|
end
|
|
86
105
|
end
|
|
87
106
|
|
data/spec/spec_helper.rb
CHANGED
|
@@ -101,7 +101,11 @@ CONFIG = {
|
|
|
101
101
|
heartbeat_frequency: 180,
|
|
102
102
|
user: SpecConfig.instance.uri.client_options[:user] || MONGOID_ROOT_USER.name,
|
|
103
103
|
password: SpecConfig.instance.uri.client_options[:password] || MONGOID_ROOT_USER.password,
|
|
104
|
-
auth_source: Mongo::Database::ADMIN
|
|
104
|
+
auth_source: Mongo::Database::ADMIN,
|
|
105
|
+
|
|
106
|
+
# so that we can run an Atlas cluster locally, and run tests with Atlas
|
|
107
|
+
# support enabled.
|
|
108
|
+
direct_connection: SpecConfig.instance.uri.uri_options[:direct_connection]
|
|
105
109
|
)
|
|
106
110
|
}
|
|
107
111
|
},
|
|
@@ -46,4 +46,257 @@ module Crypt
|
|
|
46
46
|
field :vin, type: String, encrypt: true
|
|
47
47
|
field :make, type: String
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
# An encrypted model whose database name is a callable resolving to a
|
|
51
|
+
# constant. The callable form of :database is supported by store_in, but the
|
|
52
|
+
# encryption schema map is keyed by namespace and built once, so this model's
|
|
53
|
+
# namespace is not known when the map is generated.
|
|
54
|
+
class DynamicCar
|
|
55
|
+
include Mongoid::Document
|
|
56
|
+
|
|
57
|
+
store_in database: -> { 'vehicles_dynamic' }
|
|
58
|
+
|
|
59
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
60
|
+
|
|
61
|
+
field :vin, type: String, encrypt: true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# An encrypted model using the documented multi-tenant idiom, where the
|
|
65
|
+
# database name is only known once a tenant is selected. There is no correct
|
|
66
|
+
# value to resolve at client construction time.
|
|
67
|
+
class TenantCar
|
|
68
|
+
include Mongoid::Document
|
|
69
|
+
|
|
70
|
+
# The fallback keeps this model usable by tasks that iterate every model,
|
|
71
|
+
# such as Mongoid::Tasks::Database.create_collections. Mongoid raises
|
|
72
|
+
# NoMethodError when a callable :database resolves to nil.
|
|
73
|
+
store_in database: -> { Thread.current[:tenant_database] || 'vehicles_no_tenant' }
|
|
74
|
+
|
|
75
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
76
|
+
|
|
77
|
+
field :vin, type: String, encrypt: true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# An encrypted model that is embedded by more than one parent, and more than
|
|
81
|
+
# once by the same parent.
|
|
82
|
+
class Token
|
|
83
|
+
include Mongoid::Document
|
|
84
|
+
|
|
85
|
+
field :value, type: String, encrypt: { deterministic: true }
|
|
86
|
+
|
|
87
|
+
embedded_in :tokenized, polymorphic: true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# A parent whose encrypted data lives entirely in an embedded model: it has
|
|
91
|
+
# no encrypted field of its own and no encrypt_with.
|
|
92
|
+
class Wallet
|
|
93
|
+
include Mongoid::Document
|
|
94
|
+
|
|
95
|
+
embeds_one :token, class_name: 'Crypt::Token', as: :tokenized
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Two parents embedding the same encrypted model.
|
|
99
|
+
class Vault
|
|
100
|
+
include Mongoid::Document
|
|
101
|
+
|
|
102
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q=='
|
|
103
|
+
|
|
104
|
+
embeds_one :token, class_name: 'Crypt::Token', as: :tokenized
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class Chest
|
|
108
|
+
include Mongoid::Document
|
|
109
|
+
|
|
110
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q=='
|
|
111
|
+
|
|
112
|
+
embeds_one :token, class_name: 'Crypt::Token', as: :tokenized
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A parent embedding the same encrypted model through two relations.
|
|
116
|
+
class Ledger
|
|
117
|
+
include Mongoid::Document
|
|
118
|
+
|
|
119
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q=='
|
|
120
|
+
|
|
121
|
+
embeds_one :primary_token, class_name: 'Crypt::Token', as: :tokenized
|
|
122
|
+
embeds_one :backup_token, class_name: 'Crypt::Token', as: :tokenized
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Three levels of nesting, where the middle level has no encrypted field of
|
|
126
|
+
# its own.
|
|
127
|
+
class Owner
|
|
128
|
+
include Mongoid::Document
|
|
129
|
+
|
|
130
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q=='
|
|
131
|
+
|
|
132
|
+
embeds_one :account, class_name: 'Crypt::Account'
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class Account
|
|
136
|
+
include Mongoid::Document
|
|
137
|
+
|
|
138
|
+
embedded_in :owner, class_name: 'Crypt::Owner'
|
|
139
|
+
embeds_one :credential, class_name: 'Crypt::Credential'
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class Credential
|
|
143
|
+
include Mongoid::Document
|
|
144
|
+
|
|
145
|
+
field :secret, type: String, encrypt: { deterministic: true }
|
|
146
|
+
|
|
147
|
+
embedded_in :account, class_name: 'Crypt::Account'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# A model that embeds itself. The walk over embedded relations has to stop
|
|
151
|
+
# here, or generating the map never terminates.
|
|
152
|
+
class Comment
|
|
153
|
+
include Mongoid::Document
|
|
154
|
+
|
|
155
|
+
field :body, type: String, encrypt: { deterministic: true }
|
|
156
|
+
|
|
157
|
+
embedded_in :commentable, polymorphic: true
|
|
158
|
+
embeds_one :reply, class_name: 'Crypt::Comment', as: :commentable
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class Article
|
|
162
|
+
include Mongoid::Document
|
|
163
|
+
|
|
164
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q=='
|
|
165
|
+
|
|
166
|
+
embeds_one :comment, class_name: 'Crypt::Comment', as: :commentable
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# A parent with no encrypted field of its own: the encrypted field lives on
|
|
170
|
+
# the embedded model. Used by the integration specs, so the embedded model
|
|
171
|
+
# carries a key id.
|
|
172
|
+
class Folder
|
|
173
|
+
include Mongoid::Document
|
|
174
|
+
|
|
175
|
+
embeds_one :note, class_name: 'Crypt::Note'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class Note
|
|
179
|
+
include Mongoid::Document
|
|
180
|
+
|
|
181
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
182
|
+
|
|
183
|
+
field :text, type: String, encrypt: true
|
|
184
|
+
|
|
185
|
+
embedded_in :folder, class_name: 'Crypt::Folder'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# A model naming an embedded class that is never defined. The association
|
|
189
|
+
# cannot be used, so nothing is ever embedded through it, but every model in
|
|
190
|
+
# the application is walked when the schema map is generated.
|
|
191
|
+
class Drawer
|
|
192
|
+
include Mongoid::Document
|
|
193
|
+
|
|
194
|
+
embeds_one :missing_note, class_name: 'Crypt::MissingNote'
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The same, on a model that is encrypted itself, so the walk descends into
|
|
198
|
+
# its relations.
|
|
199
|
+
class Cabinet
|
|
200
|
+
include Mongoid::Document
|
|
201
|
+
|
|
202
|
+
field :label, type: String, encrypt: { deterministic: true }
|
|
203
|
+
|
|
204
|
+
embeds_one :missing_note, class_name: 'Crypt::MissingNote'
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Two unrelated models stored in one collection, each with its own encrypted
|
|
208
|
+
# field.
|
|
209
|
+
class Badge
|
|
210
|
+
include Mongoid::Document
|
|
211
|
+
|
|
212
|
+
store_in collection: 'crypt_badges'
|
|
213
|
+
|
|
214
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
215
|
+
|
|
216
|
+
field :serial, type: String, encrypt: true
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class BadgeScan
|
|
220
|
+
include Mongoid::Document
|
|
221
|
+
|
|
222
|
+
store_in collection: 'crypt_badges'
|
|
223
|
+
|
|
224
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
225
|
+
|
|
226
|
+
field :holder, type: String, encrypt: true
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Single collection inheritance. Only Member is registered in
|
|
230
|
+
# Mongoid.models; the subclasses are not.
|
|
231
|
+
class Member
|
|
232
|
+
include Mongoid::Document
|
|
233
|
+
|
|
234
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
235
|
+
|
|
236
|
+
field :ssn, type: String, encrypt: true
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
class Employee < Member
|
|
240
|
+
field :salary, type: String, encrypt: true
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class Contractor < Member
|
|
244
|
+
field :rate, type: String, encrypt: true
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Single collection inheritance where only the subclass is encrypted.
|
|
248
|
+
class Listing
|
|
249
|
+
include Mongoid::Document
|
|
250
|
+
|
|
251
|
+
field :title, type: String
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class PrivateListing < Listing
|
|
255
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
256
|
+
|
|
257
|
+
field :owner_ssn, type: String, encrypt: true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# An embeds_one target whose subclass adds an encrypted field.
|
|
261
|
+
class Box
|
|
262
|
+
include Mongoid::Document
|
|
263
|
+
|
|
264
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
265
|
+
|
|
266
|
+
embeds_one :item, class_name: 'Crypt::Item'
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class Item
|
|
270
|
+
include Mongoid::Document
|
|
271
|
+
|
|
272
|
+
field :label, type: String, encrypt: true
|
|
273
|
+
|
|
274
|
+
embedded_in :box, class_name: 'Crypt::Box'
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class SealedItem < Item
|
|
278
|
+
field :seal, type: String, encrypt: true
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# An embeds_one target with no encrypted field, whose subclass has one.
|
|
282
|
+
# Nothing on Locker or Content declares encryption.
|
|
283
|
+
class Locker
|
|
284
|
+
include Mongoid::Document
|
|
285
|
+
|
|
286
|
+
embeds_one :content, class_name: 'Crypt::Content'
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
class Content
|
|
290
|
+
include Mongoid::Document
|
|
291
|
+
|
|
292
|
+
field :label, type: String
|
|
293
|
+
|
|
294
|
+
embedded_in :locker, class_name: 'Crypt::Locker'
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
class SecretContent < Content
|
|
298
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
299
|
+
|
|
300
|
+
field :code, type: String, encrypt: true
|
|
301
|
+
end
|
|
49
302
|
end
|
|
@@ -22,4 +22,69 @@ module ModifierModels
|
|
|
22
22
|
embedded_in :book, class_name: 'ModifierModels::Book'
|
|
23
23
|
field :text, type: String
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
class Roster
|
|
27
|
+
include Mongoid::Document
|
|
28
|
+
|
|
29
|
+
embeds_many :players, class_name: 'ModifierModels::Player'
|
|
30
|
+
accepts_nested_attributes_for :players, allow_destroy: true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Player
|
|
34
|
+
include Mongoid::Document
|
|
35
|
+
|
|
36
|
+
embedded_in :roster, class_name: 'ModifierModels::Roster'
|
|
37
|
+
embeds_one :medal, class_name: 'ModifierModels::Medal'
|
|
38
|
+
has_and_belongs_to_many :badges, inverse_of: nil, class_name: 'ModifierModels::Badge'
|
|
39
|
+
embeds_many :awards, class_name: 'ModifierModels::Award'
|
|
40
|
+
accepts_nested_attributes_for :awards, allow_destroy: true
|
|
41
|
+
field :nickname, type: String
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Award
|
|
45
|
+
include Mongoid::Document
|
|
46
|
+
|
|
47
|
+
embedded_in :player, class_name: 'ModifierModels::Player'
|
|
48
|
+
has_and_belongs_to_many :badges, inverse_of: nil, class_name: 'ModifierModels::Badge'
|
|
49
|
+
field :name, type: String
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Medal
|
|
53
|
+
include Mongoid::Document
|
|
54
|
+
|
|
55
|
+
embedded_in :player, class_name: 'ModifierModels::Player'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Badge
|
|
59
|
+
include Mongoid::Document
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Cabinet
|
|
63
|
+
include Mongoid::Document
|
|
64
|
+
|
|
65
|
+
embeds_one :drawer, class_name: 'ModifierModels::Drawer', autobuild: true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Drawer
|
|
69
|
+
include Mongoid::Document
|
|
70
|
+
|
|
71
|
+
field :matcher, type: String
|
|
72
|
+
embedded_in :cabinet, class_name: 'ModifierModels::Cabinet'
|
|
73
|
+
embeds_many :items, class_name: 'ModifierModels::Item'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Item
|
|
77
|
+
include Mongoid::Document
|
|
78
|
+
|
|
79
|
+
field :name, type: String
|
|
80
|
+
embedded_in :drawer, class_name: 'ModifierModels::Drawer'
|
|
81
|
+
embeds_many :widgets, class_name: 'ModifierModels::Widget'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class Widget
|
|
85
|
+
include Mongoid::Document
|
|
86
|
+
|
|
87
|
+
field :label, type: String
|
|
88
|
+
embedded_in :item, class_name: 'ModifierModels::Item'
|
|
89
|
+
end
|
|
25
90
|
end
|