mongoid 5.0.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/lib/mongoid/changeable.rb +1 -1
  7. data/lib/mongoid/clients.rb +1 -0
  8. data/lib/mongoid/clients/options.rb +119 -7
  9. data/lib/mongoid/config.rb +7 -0
  10. data/lib/mongoid/config/options.rb +15 -0
  11. data/lib/mongoid/contextual/geo_near.rb +12 -0
  12. data/lib/mongoid/contextual/mongo.rb +6 -0
  13. data/lib/mongoid/criteria.rb +2 -56
  14. data/lib/mongoid/criteria/findable.rb +4 -1
  15. data/lib/mongoid/criteria/includable.rb +142 -0
  16. data/lib/mongoid/criteria/modifiable.rb +13 -1
  17. data/lib/mongoid/document.rb +21 -0
  18. data/lib/mongoid/fields/foreign_key.rb +5 -1
  19. data/lib/mongoid/fields/localized.rb +16 -1
  20. data/lib/mongoid/fields/validators/macro.rb +1 -0
  21. data/lib/mongoid/findable.rb +1 -0
  22. data/lib/mongoid/loggable.rb +1 -1
  23. data/lib/mongoid/matchable.rb +6 -1
  24. data/lib/mongoid/persistable.rb +2 -2
  25. data/lib/mongoid/relations/eager.rb +12 -5
  26. data/lib/mongoid/relations/eager/base.rb +3 -1
  27. data/lib/mongoid/relations/referenced/many.rb +39 -6
  28. data/lib/mongoid/relations/targets/enumerable.rb +3 -3
  29. data/lib/mongoid/scopable.rb +5 -2
  30. data/lib/mongoid/version.rb +1 -1
  31. data/spec/app/models/address.rb +2 -0
  32. data/spec/app/models/agent.rb +2 -0
  33. data/spec/app/models/alert.rb +2 -0
  34. data/spec/app/models/post.rb +1 -0
  35. data/spec/config/mongoid.yml +1 -0
  36. data/spec/mongoid/changeable_spec.rb +1 -1
  37. data/spec/mongoid/clients/options_spec.rb +57 -0
  38. data/spec/mongoid/config_spec.rb +38 -0
  39. data/spec/mongoid/contextual/geo_near_spec.rb +19 -0
  40. data/spec/mongoid/criteria/findable_spec.rb +11 -0
  41. data/spec/mongoid/criteria/modifiable_spec.rb +126 -0
  42. data/spec/mongoid/criteria_spec.rb +81 -5
  43. data/spec/mongoid/document_spec.rb +56 -0
  44. data/spec/mongoid/fields/foreign_key_spec.rb +23 -0
  45. data/spec/mongoid/fields/localized_spec.rb +32 -14
  46. data/spec/mongoid/matchable_spec.rb +127 -1
  47. data/spec/mongoid/persistable_spec.rb +24 -0
  48. data/spec/mongoid/relations/embedded/many_spec.rb +16 -0
  49. data/spec/mongoid/relations/referenced/many_spec.rb +60 -0
  50. data/spec/mongoid/relations/referenced/many_to_many_spec.rb +40 -0
  51. data/spec/mongoid/scopable_spec.rb +67 -0
  52. data/spec/spec_helper.rb +4 -0
  53. data/spec/support/authorization.rb +2 -1
  54. metadata +6 -5
  55. metadata.gz.sig +0 -0
@@ -437,4 +437,23 @@ describe Mongoid::Contextual::GeoNear do
437
437
  expect(geo_near.time).to_not be_nil
438
438
  end
439
439
  end
440
+
441
+ describe "#empty_and_chainable" do
442
+
443
+ let!(:collection) do
444
+ Bar.collection
445
+ end
446
+
447
+ let(:criteria) do
448
+ Bar.all
449
+ end
450
+
451
+ let(:geo_near) do
452
+ described_class.new(collection, criteria, [ 52, 13 ])
453
+ end
454
+
455
+ it "returns true" do
456
+ expect(geo_near.empty_and_chainable?).to be(true)
457
+ end
458
+ end
440
459
  end
@@ -54,6 +54,17 @@ describe Mongoid::Criteria::Findable do
54
54
  it "returns the matching document" do
55
55
  expect(found).to eq(band)
56
56
  end
57
+
58
+ context "when finding by a JSON-dumped id" do
59
+
60
+ let(:found) do
61
+ Band.find(JSON.load(JSON.dump(band.id)))
62
+ end
63
+
64
+ it "properly parses the id format" do
65
+ expect(found).to eq(band)
66
+ end
67
+ end
57
68
  end
58
69
 
59
70
  context "when the id does not match" do
@@ -1227,4 +1227,130 @@ describe Mongoid::Criteria::Modifiable do
1227
1227
  end
1228
1228
  end
1229
1229
  end
1230
+
1231
+ describe '#create_with' do
1232
+
1233
+ context 'when called on the class' do
1234
+
1235
+ let(:attrs) do
1236
+ { 'username' => 'Turnip' }
1237
+ end
1238
+
1239
+ it 'returns a criteria with the defined attributes' do
1240
+ expect(Person.create_with(attrs).selector).to eq(attrs)
1241
+ end
1242
+
1243
+ context 'when a method is chained' do
1244
+
1245
+ context 'when a write method is chained' do
1246
+
1247
+ it 'executes the method' do
1248
+ expect(Person.create_with(attrs).new.username).to eq('Turnip')
1249
+ end
1250
+ end
1251
+
1252
+ context 'when a write method is chained' do
1253
+
1254
+ let(:query) do
1255
+ { 'age' => 50 }
1256
+ end
1257
+
1258
+ let(:new_person) do
1259
+ Person.create_with(attrs).find_or_create_by(query)
1260
+ end
1261
+
1262
+ it 'executes the write' do
1263
+ expect(new_person.username).to eq('Turnip')
1264
+ expect(new_person.age).to eq(50)
1265
+ end
1266
+
1267
+ context 'when the attributes are shared with the write method args' do
1268
+
1269
+ let(:query) do
1270
+ { 'username' => 'Beet', 'age' => 50 }
1271
+ end
1272
+
1273
+ let(:new_person) do
1274
+ Person.create_with(attrs).find_or_create_by(query)
1275
+ end
1276
+
1277
+ it 'gives the write method args precedence' do
1278
+ # @todo: uncomment when MONGOID-4193 is closed
1279
+ #expect(new_person.username).to eq('Beet')
1280
+ expect(new_person.age).to eq(50)
1281
+ end
1282
+ end
1283
+ end
1284
+ end
1285
+ end
1286
+
1287
+ context 'when called on a criteria' do
1288
+
1289
+ let(:criteria_selector) do
1290
+ { 'username' => 'Artichoke', 'age' => 25 }
1291
+ end
1292
+
1293
+ let(:criteria) do
1294
+ Person.where(criteria_selector)
1295
+ end
1296
+
1297
+ context 'when the original criteria shares attributes with the attribute args' do
1298
+
1299
+ context 'when all the original attributes are shared with the new attributes' do
1300
+
1301
+ let(:attrs) do
1302
+ { 'username' => 'Beet', 'age' => 50 }
1303
+ end
1304
+
1305
+ it 'overwrites all the original attributes' do
1306
+ expect(criteria.create_with(attrs).selector).to eq(attrs)
1307
+ end
1308
+ end
1309
+ end
1310
+
1311
+ context 'when only some of the original attributes are shared with the attribute args' do
1312
+
1313
+ let(:attrs) do
1314
+ { 'username' => 'Beet' }
1315
+ end
1316
+
1317
+ it 'only overwrites the shared attributes' do
1318
+ expect(criteria.create_with(attrs).selector).to eq(criteria_selector.merge!(attrs))
1319
+ end
1320
+ end
1321
+
1322
+ context 'when a method is chained' do
1323
+
1324
+ let(:attrs) do
1325
+ { 'username' => 'Turnip' }
1326
+ end
1327
+
1328
+ let(:query) do
1329
+ { 'username' => 'Beet', 'age' => 50 }
1330
+ end
1331
+
1332
+ context 'when a write method is chained' do
1333
+
1334
+ it 'executes the method' do
1335
+ # @todo: uncomment when MONGOID-4193 is closed
1336
+ #expect(criteria.create_with(attrs).new.username).to eq('Beet')
1337
+ expect(criteria.create_with(attrs).new.age).to eq(25)
1338
+ end
1339
+ end
1340
+
1341
+ context 'when a write method is chained' do
1342
+
1343
+ let(:new_person) do
1344
+ criteria.create_with(attrs).find_or_create_by(query)
1345
+ end
1346
+
1347
+ it 'executes the query' do
1348
+ # @todo: uncomment when MONGOID-4193 is closed
1349
+ #expect(new_person.username).to eq('Beet')
1350
+ #expect(new_person.age).to eq(50)
1351
+ end
1352
+ end
1353
+ end
1354
+ end
1355
+ end
1230
1356
  end
@@ -1147,12 +1147,72 @@ describe Mongoid::Criteria do
1147
1147
  end
1148
1148
  end
1149
1149
 
1150
- context "when providing a hash" do
1150
+ context "when providing a list of associations" do
1151
1151
 
1152
- it "raises an error" do
1153
- expect {
1154
- Person.includes(preferences: :members)
1155
- }.to raise_error(Mongoid::Errors::InvalidIncludes)
1152
+ let!(:user) do
1153
+ User.create(posts: [ post1 ], descriptions: [ description1 ])
1154
+ end
1155
+
1156
+ let!(:post1) do
1157
+ Post.create
1158
+ end
1159
+
1160
+ let!(:description1) do
1161
+ Description.create(details: 1)
1162
+ end
1163
+
1164
+ let(:result) do
1165
+ User.includes(:posts, :descriptions).first
1166
+ end
1167
+
1168
+ it "executes the query" do
1169
+ expect(result).to eq(user)
1170
+ end
1171
+
1172
+ it "includes the related objects" do
1173
+ expect(result.posts).to eq([ post1 ])
1174
+ expect(result.descriptions).to eq([ description1 ])
1175
+ end
1176
+ end
1177
+
1178
+ context "when providing a nested association" do
1179
+
1180
+ let!(:user) do
1181
+ User.create
1182
+ end
1183
+
1184
+ before do
1185
+ p = Post.create(alerts: [ Alert.create ])
1186
+ user.posts = [ p ]
1187
+ user.save
1188
+ end
1189
+
1190
+ let(:result) do
1191
+ User.includes(:posts => [:alerts]).first
1192
+ end
1193
+
1194
+ it "executes the query" do
1195
+ expect(result).to eq(user)
1196
+ end
1197
+
1198
+ it "includes the related objects" do
1199
+ expect(result.posts.size).to eq(1)
1200
+ expect(result.posts.first.alerts.size).to eq(1)
1201
+ end
1202
+ end
1203
+
1204
+ context "when providing a deeply nested association" do
1205
+
1206
+ let!(:user) do
1207
+ User.create
1208
+ end
1209
+
1210
+ let(:results) do
1211
+ User.includes(:posts => [{ :alerts => :items }]).to_a
1212
+ end
1213
+
1214
+ it "executes the query" do
1215
+ expect(results.first).to eq(user)
1156
1216
  end
1157
1217
  end
1158
1218
 
@@ -1759,6 +1819,22 @@ describe Mongoid::Criteria do
1759
1819
  person.preferences.create(name: "two")
1760
1820
  end
1761
1821
 
1822
+ context "when one of the related items is deleted" do
1823
+
1824
+ before do
1825
+ person.preferences = [ preference_one, preference_two ]
1826
+ preference_two.delete
1827
+ end
1828
+
1829
+ let(:criteria) do
1830
+ Person.where(id: person.id).includes(:preferences)
1831
+ end
1832
+
1833
+ it "only loads the existing related items" do
1834
+ expect(criteria.entries.first.preferences).to eq([ preference_one ])
1835
+ end
1836
+ end
1837
+
1762
1838
  context "when the criteria has no options" do
1763
1839
 
1764
1840
  let!(:criteria) do
@@ -441,6 +441,62 @@ describe Mongoid::Document do
441
441
  end
442
442
  end
443
443
 
444
+ describe "#as_json" do
445
+
446
+ let!(:person) do
447
+ Person.new(title: "Sir")
448
+ end
449
+
450
+ context "when no options are provided" do
451
+
452
+ it "does not apply any options" do
453
+ expect(person.as_json["title"]).to eq("Sir")
454
+ expect(person.as_json["age"]).to eq(100)
455
+ end
456
+
457
+ context "when options for the super method are provided" do
458
+
459
+ let(:options) do
460
+ { only: :title }
461
+ end
462
+
463
+ it "passes the options through to the super method" do
464
+ expect(person.as_json(options)["title"]).to eq("Sir")
465
+ expect(person.as_json(options).keys).not_to include("age")
466
+ end
467
+ end
468
+ end
469
+
470
+ context "when the Mongoid-specific options are provided" do
471
+
472
+ let(:options) do
473
+ { compact: true }
474
+ end
475
+
476
+ it "applies the Mongoid-specific options" do
477
+ expect(person.as_json(options)["title"]).to eq("Sir")
478
+ expect(person.as_json(options)["age"]).to eq(100)
479
+ expect(person.as_json(options).keys).not_to include("lunch_time")
480
+ end
481
+
482
+ context "when options for the super method are provided" do
483
+
484
+ let(:options) do
485
+ { compact: true, only: [:title, :pets, :ssn] }
486
+ end
487
+
488
+ it "passes the options through to the super method" do
489
+ expect(person.as_json(options)["title"]).to eq("Sir")
490
+ expect(person.as_json(options)["pets"]).to eq(false)
491
+ end
492
+
493
+ it "applies the Mongoid-specific options" do
494
+ expect(person.as_json(options).keys).not_to include("ssn")
495
+ end
496
+ end
497
+ end
498
+ end
499
+
444
500
  describe "#as_document" do
445
501
 
446
502
  let!(:person) do
@@ -444,6 +444,29 @@ describe Mongoid::Fields::ForeignKey do
444
444
  end
445
445
  end
446
446
  end
447
+
448
+ context "when the metadata is polymoprhic" do
449
+
450
+ let(:metadata) do
451
+ Agent.reflect_on_association(:names)
452
+ end
453
+
454
+ let(:field) do
455
+ described_class.new(:nameable_id, type: Object, metadata: metadata)
456
+ end
457
+
458
+ let(:value) do
459
+ BSON::ObjectId.new().to_s
460
+ end
461
+
462
+ let(:evolved) do
463
+ field.evolve(value)
464
+ end
465
+
466
+ it "does not change the foreign key" do
467
+ expect(evolved).to be(value)
468
+ end
469
+ end
447
470
  end
448
471
 
449
472
  describe "#lazy?" do
@@ -269,32 +269,50 @@ describe Mongoid::Fields::Localized do
269
269
  ::I18n.fallbacks[:de] = [ :de, :en, :es ]
270
270
  end
271
271
 
272
- context "when the first fallback translation exists" do
272
+ context 'when fallbacks are enabled' do
273
273
 
274
- let(:value) do
275
- field.demongoize({ "en" => 1 })
276
- end
274
+ context "when the first fallback translation exists" do
277
275
 
278
- it "returns the fallback translation" do
279
- expect(value).to eq(1)
276
+ let(:value) do
277
+ field.demongoize({ "en" => 1 })
278
+ end
279
+
280
+ it "returns the fallback translation" do
281
+ expect(value).to eq(1)
282
+ end
280
283
  end
281
- end
282
284
 
283
- context "when another fallback translation exists" do
285
+ context "when another fallback translation exists" do
284
286
 
285
- let(:value) do
286
- field.demongoize({ "es" => 100 })
287
+ let(:value) do
288
+ field.demongoize({ "es" => 100 })
289
+ end
290
+
291
+ it "returns the fallback translation" do
292
+ expect(value).to eq(100)
293
+ end
287
294
  end
288
295
 
289
- it "returns the fallback translation" do
290
- expect(value).to eq(100)
296
+ context "when the fallback translation does not exist" do
297
+
298
+ let(:value) do
299
+ field.demongoize({ "fr" => 50 })
300
+ end
301
+
302
+ it "returns nil" do
303
+ expect(value).to be_nil
304
+ end
291
305
  end
292
306
  end
293
307
 
294
- context "when the fallback translation does not exist" do
308
+ context 'when fallbacks are disabled' do
309
+
310
+ let(:field) do
311
+ described_class.new(:description, localize: true, type: Integer, fallbacks: false)
312
+ end
295
313
 
296
314
  let(:value) do
297
- field.demongoize({ "fr" => 50 })
315
+ field.demongoize({ "es" => 100 })
298
316
  end
299
317
 
300
318
  it "returns nil" do
@@ -149,7 +149,8 @@ describe Mongoid::Matchable do
149
149
  Address.new(
150
150
  services: ["first", "second", "third"],
151
151
  number: 100,
152
- map: { key: "value" }
152
+ map: { key: "value" },
153
+ street: "Clarkenwell Road"
153
154
  )
154
155
  end
155
156
 
@@ -303,6 +304,131 @@ describe Mongoid::Matchable do
303
304
  end
304
305
  end
305
306
 
307
+ context "with a $not selector" do
308
+
309
+ context "regexes" do
310
+
311
+ context "when the predicate matches" do
312
+
313
+ let(:selector) do
314
+ {
315
+ street: {"$not" => /Avenue/}
316
+ }
317
+ end
318
+
319
+ it "returns true" do
320
+ expect(document.matches?(selector)).to be true
321
+ end
322
+ end
323
+
324
+ context "when the predicate does not match" do
325
+
326
+ let(:selector) do
327
+ {
328
+ street: {"$not" => /Road/}
329
+ }
330
+ end
331
+
332
+ it "returns false" do
333
+ expect(document.matches?(selector)).to be false
334
+ end
335
+ end
336
+ end
337
+
338
+ context "other operators" do
339
+
340
+ context "numerical comparisons" do
341
+
342
+ context "$lt and $gt" do
343
+
344
+ context "when the predicate matches" do
345
+
346
+ let(:selector) do
347
+ {
348
+ number: {"$not" => {"$lt" => 0}}
349
+ }
350
+ end
351
+
352
+ it "returns true" do
353
+ expect(document.matches?(selector)).to be true
354
+ end
355
+ end
356
+
357
+ context "when the predicate does not match" do
358
+
359
+ let(:selector) do
360
+ {
361
+ number: {"$not" => {"$gt" => 50}}
362
+ }
363
+ end
364
+
365
+ it "returns false" do
366
+ expect(document.matches?(selector)).to be false
367
+ end
368
+ end
369
+ end
370
+
371
+ context "$in" do
372
+
373
+ context "when the predicate matches" do
374
+
375
+ let(:selector) do
376
+ {
377
+ number: {"$not" => {"$in" => [10]}}
378
+ }
379
+ end
380
+
381
+ it "returns true" do
382
+ expect(document.matches?(selector)).to be true
383
+ end
384
+ end
385
+
386
+ context "when the predicate does not match" do
387
+
388
+ let(:selector) do
389
+ {
390
+ number: {"$not" => {"$in" => [100]}}
391
+ }
392
+ end
393
+
394
+ it "returns false" do
395
+ expect(document.matches?(selector)).to be false
396
+ end
397
+ end
398
+ end
399
+ end
400
+ end
401
+
402
+ context "symbol keys" do
403
+
404
+ context "when the predicate matches" do
405
+
406
+ let(:selector) do
407
+ {
408
+ street: {:$not => /Avenue/}
409
+ }
410
+ end
411
+
412
+ it "returns true" do
413
+ expect(document.matches?(selector)).to be true
414
+ end
415
+ end
416
+
417
+ context "when the predicate does not match" do
418
+
419
+ let(:selector) do
420
+ {
421
+ street: {:$not => /Road/}
422
+ }
423
+ end
424
+
425
+ it "returns false" do
426
+ expect(document.matches?(selector)).to be false
427
+ end
428
+ end
429
+ end
430
+ end
431
+
306
432
  context "with an $in selector" do
307
433
 
308
434
  context "when the attributes match" do