mongoid 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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