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.
- checksums.yaml +4 -4
- data/lib/config/locales/en.yml +49 -0
- data/lib/mongoid/association/accessors.rb +9 -1
- 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 +57 -100
- 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.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 +40 -0
- data/lib/mongoid/association.rb +7 -0
- data/lib/mongoid/clients/factory.rb +7 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +36 -19
- 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/encryptable.rb +46 -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/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 +28 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +4 -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/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 +1 -1
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistence_context.rb +35 -0
- data/lib/mongoid/search_indexable.rb +64 -23
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +151 -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/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/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/attributes/nested_spec.rb +249 -8
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -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 +875 -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 +61 -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/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +295 -9
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +25 -0
- data/spec/mongoid/touchable_spec_models.rb +10 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +157 -0
- metadata +52 -2
|
@@ -72,6 +72,19 @@ describe Mongoid::Config::Encryption do
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# Models are registered in class load order, and Rails eager loads
|
|
76
|
+
# app/models alphabetically, so a child whose file name sorts before
|
|
77
|
+
# its parent's arrives first.
|
|
78
|
+
context 'when the embedded model comes before its parent' do
|
|
79
|
+
let(:models) do
|
|
80
|
+
[ Crypt::Insurance, Crypt::Patient ]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'returns a map of encryption schemas' do
|
|
84
|
+
expect(encryption_schema_map).to eq(expected_schema_map)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
75
88
|
context 'and fields do not have encryption options' do
|
|
76
89
|
let(:models) do
|
|
77
90
|
[ Crypt::Car ]
|
|
@@ -106,6 +119,7 @@ describe Mongoid::Config::Encryption do
|
|
|
106
119
|
let(:expected_schema_map) do
|
|
107
120
|
{
|
|
108
121
|
'mongoid_test.crypt_users' => {
|
|
122
|
+
'bsonType' => 'object',
|
|
109
123
|
'properties' => {
|
|
110
124
|
'name' => {
|
|
111
125
|
'encrypt' => {
|
|
@@ -145,5 +159,219 @@ describe Mongoid::Config::Encryption do
|
|
|
145
159
|
expect(encryption_schema_map).to eq({})
|
|
146
160
|
end
|
|
147
161
|
end
|
|
162
|
+
|
|
163
|
+
# A field left out of the schema map is written in plaintext, with no
|
|
164
|
+
# exception and nothing logged, so an omission here is silent data
|
|
165
|
+
# exposure rather than a broken feature.
|
|
166
|
+
context 'when the encrypted fields are on an embedded model' do
|
|
167
|
+
let(:token_properties) do
|
|
168
|
+
{
|
|
169
|
+
'bsonType' => 'object',
|
|
170
|
+
'properties' => {
|
|
171
|
+
'value' => {
|
|
172
|
+
'encrypt' => {
|
|
173
|
+
'bsonType' => 'string',
|
|
174
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'when two models embed the same encrypted model' do
|
|
182
|
+
let(:models) do
|
|
183
|
+
[ Crypt::Vault, Crypt::Chest ]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'maps the embedded model for the first parent' do
|
|
187
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
|
|
188
|
+
.to eq(token_properties)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'maps the embedded model for the second parent' do
|
|
192
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_chests', 'properties', 'token'))
|
|
193
|
+
.to eq(token_properties)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
context 'when one model embeds the same encrypted model twice' do
|
|
198
|
+
let(:models) do
|
|
199
|
+
[ Crypt::Ledger ]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'maps the first relation' do
|
|
203
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'primary_token'))
|
|
204
|
+
.to eq(token_properties)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'maps the second relation' do
|
|
208
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'backup_token'))
|
|
209
|
+
.to eq(token_properties)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# relation_class constantizes, and a polymorphic embedded_in has no class
|
|
214
|
+
# to resolve, so the walk has to look at the relation type first.
|
|
215
|
+
context 'when the embedded model is embedded polymorphically' do
|
|
216
|
+
let(:models) do
|
|
217
|
+
[ Crypt::Vault ]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'does not raise' do
|
|
221
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
context 'when the embedded model comes before its parent' do
|
|
226
|
+
let(:models) do
|
|
227
|
+
[ Crypt::Token, Crypt::Vault ]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'maps the embedded model' do
|
|
231
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
|
|
232
|
+
.to eq(token_properties)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'when the parent has no encrypted field of its own' do
|
|
237
|
+
let(:models) do
|
|
238
|
+
[ Crypt::Wallet, Crypt::Token ]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it 'maps the embedded model' do
|
|
242
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_wallets', 'properties', 'token'))
|
|
243
|
+
.to eq(token_properties)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
context 'when the encrypted model is nested three levels deep' do
|
|
248
|
+
let(:account_properties) do
|
|
249
|
+
{
|
|
250
|
+
'bsonType' => 'object',
|
|
251
|
+
'properties' => {
|
|
252
|
+
'credential' => {
|
|
253
|
+
'bsonType' => 'object',
|
|
254
|
+
'properties' => {
|
|
255
|
+
'secret' => {
|
|
256
|
+
'encrypt' => {
|
|
257
|
+
'bsonType' => 'string',
|
|
258
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
context 'when the models are listed outermost first' do
|
|
268
|
+
let(:models) do
|
|
269
|
+
[ Crypt::Owner, Crypt::Account, Crypt::Credential ]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'maps the whole subtree' do
|
|
273
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
|
|
274
|
+
.to eq(account_properties)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
context 'when the middle model comes first' do
|
|
279
|
+
let(:models) do
|
|
280
|
+
[ Crypt::Account, Crypt::Owner, Crypt::Credential ]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'maps the whole subtree' do
|
|
284
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
|
|
285
|
+
.to eq(account_properties)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# The walk has to keep stopping at a model it is already inside of,
|
|
291
|
+
# otherwise a self-embedding model recurses forever.
|
|
292
|
+
context 'when a model embeds itself' do
|
|
293
|
+
let(:models) do
|
|
294
|
+
[ Crypt::Article ]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'terminates' do
|
|
298
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it 'maps the embedded model without descending into the cycle' do
|
|
302
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_articles', 'properties', 'comment')).to eq(
|
|
303
|
+
'bsonType' => 'object',
|
|
304
|
+
'properties' => {
|
|
305
|
+
'body' => {
|
|
306
|
+
'encrypt' => {
|
|
307
|
+
'bsonType' => 'string',
|
|
308
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
context 'when an encrypted model has a callable database name' do
|
|
318
|
+
let(:models) do
|
|
319
|
+
[ Crypt::DynamicCar ]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'does not build a key from the unresolved callable' do
|
|
323
|
+
expect(encryption_schema_map.keys.grep(/Proc/)).to be_empty
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
it 'does not build a key that the model can never write to' do
|
|
327
|
+
expect(encryption_schema_map.keys).to all(satisfy { |key| key.count('.') == 1 })
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
context 'when an encrypted model has a callable database name that needs a tenant' do
|
|
332
|
+
let(:models) do
|
|
333
|
+
[ Crypt::TenantCar ]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Resolving the callable while the map is built is not a fix: the
|
|
337
|
+
# documented multi-tenant idiom has no correct value at client
|
|
338
|
+
# construction time. Generating the map must not depend on it.
|
|
339
|
+
it 'does not raise' do
|
|
340
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'does not pin the map to whichever tenant happens to be current' do
|
|
344
|
+
Thread.current[:tenant_database] = 'tenant_a'
|
|
345
|
+
expect(encryption_schema_map.keys).not_to include('tenant_a.crypt_tenant_cars')
|
|
346
|
+
ensure
|
|
347
|
+
Thread.current[:tenant_database] = nil
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# The map is generated over every model in the application, and a model may
|
|
352
|
+
# name an embedded class that is never defined. Resolving that name raises,
|
|
353
|
+
# which would take down every client build.
|
|
354
|
+
context 'when a model names an embedded class that is not defined' do
|
|
355
|
+
let(:models) do
|
|
356
|
+
[ Crypt::Drawer, Crypt::Cabinet ]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
it 'does not raise' do
|
|
360
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it 'leaves out the model with nothing to encrypt' do
|
|
364
|
+
expect(encryption_schema_map.keys).not_to include('mongoid_test.crypt_drawers')
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it 'still maps the encrypted fields of the model itself' do
|
|
368
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_cabinets', 'properties', 'label')).to eq(
|
|
369
|
+
'encrypt' => {
|
|
370
|
+
'bsonType' => 'string',
|
|
371
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
148
376
|
end
|
|
149
377
|
end
|
|
@@ -545,4 +545,95 @@ describe Mongoid::Contextual::Aggregable::Memory do
|
|
|
545
545
|
end
|
|
546
546
|
end
|
|
547
547
|
end
|
|
548
|
+
|
|
549
|
+
context 'when the field name is a method name' do
|
|
550
|
+
let!(:depeche) do
|
|
551
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
let(:criteria) do
|
|
555
|
+
Band.all.tap do |crit|
|
|
556
|
+
crit.documents = [ depeche ]
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it 'does not call the method for sum' do
|
|
561
|
+
expect(depeche).not_to receive(:delete)
|
|
562
|
+
expect(context.sum(:delete)).to eq(0)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
it 'does not call the method for min' do
|
|
566
|
+
expect(depeche).not_to receive(:delete)
|
|
567
|
+
expect(context.min(:delete)).to be_nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it 'does not call the method for max' do
|
|
571
|
+
expect(depeche).not_to receive(:delete)
|
|
572
|
+
expect(context.max(:delete)).to be_nil
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it 'does not call the method for avg' do
|
|
576
|
+
expect(depeche).not_to receive(:delete)
|
|
577
|
+
expect(context.avg(:delete)).to be_nil
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
it 'does not call the method for aggregates' do
|
|
581
|
+
expect(depeche).not_to receive(:delete)
|
|
582
|
+
expect(context.aggregates(:delete))
|
|
583
|
+
.to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it 'does not call a private method for avg' do
|
|
587
|
+
expect(depeche).not_to receive(:exit)
|
|
588
|
+
expect(context.avg(:exit)).to be_nil
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
context 'when the field is not defined' do
|
|
593
|
+
let!(:depeche) do
|
|
594
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
let(:criteria) do
|
|
598
|
+
Band.all.tap do |crit|
|
|
599
|
+
crit.documents = [ depeche ]
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
it 'returns zero for sum' do
|
|
604
|
+
expect(context.sum(:not_a_field)).to eq(0)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
it 'returns nil for min' do
|
|
608
|
+
expect(context.min(:not_a_field)).to be_nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
it 'returns nil for max' do
|
|
612
|
+
expect(context.max(:not_a_field)).to be_nil
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
it 'returns nil for avg' do
|
|
616
|
+
expect(context.avg(:not_a_field)).to be_nil
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
context 'when the field is the foreign key of a many to many association' do
|
|
621
|
+
let!(:preference) do
|
|
622
|
+
Preference.create!(name: 'nature')
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
let!(:person) do
|
|
626
|
+
Person.create!(preferences: [ preference ])
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
let(:criteria) do
|
|
630
|
+
Person.all.tap do |crit|
|
|
631
|
+
crit.documents = [ person ]
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it 'aggregates the stored ids' do
|
|
636
|
+
expect(context.max(:preference_ids)).to eq([ preference.id ])
|
|
637
|
+
end
|
|
638
|
+
end
|
|
548
639
|
end
|
|
@@ -556,6 +556,27 @@ describe Mongoid::Contextual::Memory do
|
|
|
556
556
|
expect(context.distinct('label.sales')).to eq([ BigDecimal('1E2') ])
|
|
557
557
|
end
|
|
558
558
|
end
|
|
559
|
+
|
|
560
|
+
context 'when the field name is a method name' do
|
|
561
|
+
let!(:person) do
|
|
562
|
+
Person.create!(ssn: 'secret-ssn')
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
let!(:address) do
|
|
566
|
+
person.addresses.create!(street: 'hobrecht')
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
let(:criteria) do
|
|
570
|
+
Address.all.tap do |crit|
|
|
571
|
+
crit.documents = [ address ]
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it 'does not call the method' do
|
|
576
|
+
expect(address).not_to receive(:destroy)
|
|
577
|
+
expect(context.distinct(:destroy)).to eq([ nil ])
|
|
578
|
+
end
|
|
579
|
+
end
|
|
559
580
|
end
|
|
560
581
|
|
|
561
582
|
describe '#each' do
|
|
@@ -1714,6 +1735,116 @@ describe Mongoid::Contextual::Memory do
|
|
|
1714
1735
|
])
|
|
1715
1736
|
end
|
|
1716
1737
|
end
|
|
1738
|
+
|
|
1739
|
+
context 'when plucking a dynamic attribute' do
|
|
1740
|
+
let(:criteria) do
|
|
1741
|
+
Band.all.tap do |crit|
|
|
1742
|
+
crit.documents = [ Band.create!(name: 'Depeche Mode', mood: 'dark') ]
|
|
1743
|
+
end
|
|
1744
|
+
end
|
|
1745
|
+
|
|
1746
|
+
it 'returns the value from the attributes' do
|
|
1747
|
+
expect(context.pluck(:mood)).to eq([ 'dark' ])
|
|
1748
|
+
end
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
context 'when the field name is a method name' do
|
|
1752
|
+
let!(:person) do
|
|
1753
|
+
Person.create!(ssn: 'secret-ssn')
|
|
1754
|
+
end
|
|
1755
|
+
|
|
1756
|
+
let!(:address) do
|
|
1757
|
+
person.addresses.create!(street: 'hobrecht')
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
let(:criteria) do
|
|
1761
|
+
Address.all.tap do |crit|
|
|
1762
|
+
crit.documents = [ address ]
|
|
1763
|
+
end
|
|
1764
|
+
end
|
|
1765
|
+
|
|
1766
|
+
it 'does not call the method' do
|
|
1767
|
+
expect(address).not_to receive(:destroy)
|
|
1768
|
+
expect(context.pluck(:destroy)).to eq([ nil ])
|
|
1769
|
+
end
|
|
1770
|
+
|
|
1771
|
+
it 'does not return the attributes hash' do
|
|
1772
|
+
expect(context.pluck(:attributes)).to eq([ nil ])
|
|
1773
|
+
end
|
|
1774
|
+
|
|
1775
|
+
it 'does not traverse to the parent document' do
|
|
1776
|
+
expect(context.pluck('_root.ssn')).to eq([ nil ])
|
|
1777
|
+
end
|
|
1778
|
+
|
|
1779
|
+
it 'does not destroy the parent document' do
|
|
1780
|
+
expect(context.pluck('_root.destroy')).to eq([ nil ])
|
|
1781
|
+
expect(Person.where(_id: person.id).count).to eq(1)
|
|
1782
|
+
end
|
|
1783
|
+
end
|
|
1784
|
+
|
|
1785
|
+
context 'when plucking a belongs_to association' do
|
|
1786
|
+
let!(:band) do
|
|
1787
|
+
Band.create!(name: 'Depeche Mode')
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
let!(:artist) do
|
|
1791
|
+
Artist.create!(band: band)
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1794
|
+
let(:criteria) do
|
|
1795
|
+
Artist.all.tap do |crit|
|
|
1796
|
+
crit.documents = [ artist ]
|
|
1797
|
+
end
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
it 'traverses to the associated document' do
|
|
1801
|
+
expect(context.pluck('band.name')).to eq([ 'Depeche Mode' ])
|
|
1802
|
+
end
|
|
1803
|
+
|
|
1804
|
+
it 'returns the document for the association name' do
|
|
1805
|
+
expect(context.pluck(:band)).to eq([ band ])
|
|
1806
|
+
end
|
|
1807
|
+
|
|
1808
|
+
it 'returns the id for the foreign key' do
|
|
1809
|
+
expect(context.pluck(:band_id)).to eq([ band.id ])
|
|
1810
|
+
end
|
|
1811
|
+
end
|
|
1812
|
+
|
|
1813
|
+
context 'when plucking the foreign key of a many to many association' do
|
|
1814
|
+
let!(:preference) do
|
|
1815
|
+
Preference.create!(name: 'nature')
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
let!(:person) do
|
|
1819
|
+
Person.create!(preferences: [ preference ])
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1822
|
+
let(:criteria) do
|
|
1823
|
+
Person.all.tap do |crit|
|
|
1824
|
+
crit.documents = [ person ]
|
|
1825
|
+
end
|
|
1826
|
+
end
|
|
1827
|
+
|
|
1828
|
+
it 'returns the stored ids' do
|
|
1829
|
+
expect(context.pluck(:preference_ids)).to eq([ [ preference.id ] ])
|
|
1830
|
+
end
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
context 'when the field path has an empty segment' do
|
|
1834
|
+
let!(:band) do
|
|
1835
|
+
Band.create!(name: 'Depeche Mode', label: Label.new(name: 'Mute'))
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
let(:criteria) do
|
|
1839
|
+
Band.all.tap do |crit|
|
|
1840
|
+
crit.documents = [ band ]
|
|
1841
|
+
end
|
|
1842
|
+
end
|
|
1843
|
+
|
|
1844
|
+
it 'returns nil' do
|
|
1845
|
+
expect(context.pluck('label..name')).to eq([ nil ])
|
|
1846
|
+
end
|
|
1847
|
+
end
|
|
1717
1848
|
end
|
|
1718
1849
|
|
|
1719
1850
|
describe '#pick' do
|
|
@@ -1770,6 +1901,27 @@ describe Mongoid::Contextual::Memory do
|
|
|
1770
1901
|
expect(picked).to be_nil
|
|
1771
1902
|
end
|
|
1772
1903
|
end
|
|
1904
|
+
|
|
1905
|
+
context 'when the field name is a method name' do
|
|
1906
|
+
let!(:person) do
|
|
1907
|
+
Person.create!(ssn: 'secret-ssn')
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1910
|
+
let!(:address) do
|
|
1911
|
+
person.addresses.create!(street: 'hobrecht')
|
|
1912
|
+
end
|
|
1913
|
+
|
|
1914
|
+
let(:criteria) do
|
|
1915
|
+
Address.all.tap do |crit|
|
|
1916
|
+
crit.documents = [ address ]
|
|
1917
|
+
end
|
|
1918
|
+
end
|
|
1919
|
+
|
|
1920
|
+
it 'does not call the method' do
|
|
1921
|
+
expect(address).not_to receive(:destroy)
|
|
1922
|
+
expect(context.pick(:destroy)).to be_nil
|
|
1923
|
+
end
|
|
1924
|
+
end
|
|
1773
1925
|
end
|
|
1774
1926
|
|
|
1775
1927
|
describe '#tally' do
|
|
@@ -2234,6 +2386,31 @@ describe Mongoid::Contextual::Memory do
|
|
|
2234
2386
|
)
|
|
2235
2387
|
end
|
|
2236
2388
|
end
|
|
2389
|
+
|
|
2390
|
+
context 'when the field name is a method name' do
|
|
2391
|
+
let!(:person) do
|
|
2392
|
+
Person.create!(ssn: 'secret-ssn')
|
|
2393
|
+
end
|
|
2394
|
+
|
|
2395
|
+
let!(:address) do
|
|
2396
|
+
person.addresses.create!(street: 'hobrecht')
|
|
2397
|
+
end
|
|
2398
|
+
|
|
2399
|
+
let(:criteria) do
|
|
2400
|
+
Address.all.tap do |crit|
|
|
2401
|
+
crit.documents = [ address ]
|
|
2402
|
+
end
|
|
2403
|
+
end
|
|
2404
|
+
|
|
2405
|
+
it 'does not call the method' do
|
|
2406
|
+
expect(address).not_to receive(:destroy)
|
|
2407
|
+
expect(context.tally(:destroy)).to eq(nil => 1)
|
|
2408
|
+
end
|
|
2409
|
+
|
|
2410
|
+
it 'does not disclose the parent document fields' do
|
|
2411
|
+
expect(context.tally('_root.ssn')).to eq(nil => 1)
|
|
2412
|
+
end
|
|
2413
|
+
end
|
|
2237
2414
|
end
|
|
2238
2415
|
|
|
2239
2416
|
describe '#inc' do
|