mongoid 7.4.3 → 7.5.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 (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/config/locales/en.yml +7 -0
  4. data/lib/mongoid/association/embedded/batchable.rb +3 -20
  5. data/lib/mongoid/association/macros.rb +20 -0
  6. data/lib/mongoid/association/referenced/has_many/enumerable.rb +12 -8
  7. data/lib/mongoid/association/referenced/has_many/proxy.rb +2 -2
  8. data/lib/mongoid/atomic/paths/embedded/many.rb +0 -19
  9. data/lib/mongoid/config.rb +6 -1
  10. data/lib/mongoid/contextual/memory.rb +144 -12
  11. data/lib/mongoid/contextual/mongo.rb +118 -26
  12. data/lib/mongoid/contextual/none.rb +45 -1
  13. data/lib/mongoid/criteria/queryable/extensions/array.rb +2 -0
  14. data/lib/mongoid/criteria/queryable/extensions/hash.rb +2 -0
  15. data/lib/mongoid/criteria/queryable/mergeable.rb +21 -0
  16. data/lib/mongoid/criteria/queryable/selectable.rb +26 -10
  17. data/lib/mongoid/criteria.rb +2 -0
  18. data/lib/mongoid/document.rb +2 -0
  19. data/lib/mongoid/equality.rb +4 -4
  20. data/lib/mongoid/errors/document_not_found.rb +23 -6
  21. data/lib/mongoid/fields.rb +145 -21
  22. data/lib/mongoid/findable.rb +20 -5
  23. data/lib/mongoid/version.rb +1 -1
  24. data/lib/mongoid/warnings.rb +29 -0
  25. data/lib/mongoid.rb +1 -0
  26. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -3
  27. data/spec/integration/i18n_fallbacks_spec.rb +15 -1
  28. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +0 -21
  29. data/spec/mongoid/association/embedded/embeds_many_models.rb +0 -121
  30. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +0 -8
  31. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +54 -0
  32. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +8 -24
  33. data/spec/mongoid/clients/options_spec.rb +1 -0
  34. data/spec/mongoid/config_spec.rb +10 -4
  35. data/spec/mongoid/contextual/memory_spec.rb +826 -65
  36. data/spec/mongoid/contextual/mongo_spec.rb +781 -18
  37. data/spec/mongoid/contextual/none_spec.rb +46 -0
  38. data/spec/mongoid/criteria/queryable/selectable_spec.rb +212 -39
  39. data/spec/mongoid/criteria_spec.rb +8 -0
  40. data/spec/mongoid/equality_spec.rb +12 -12
  41. data/spec/mongoid/errors/document_not_found_spec.rb +49 -0
  42. data/spec/mongoid/findable_spec.rb +30 -0
  43. data/spec/support/models/code.rb +2 -0
  44. data.tar.gz.sig +0 -0
  45. metadata +3 -2
  46. metadata.gz.sig +0 -0
@@ -379,36 +379,77 @@ describe Mongoid::Contextual::Memory do
379
379
 
380
380
  describe "#distinct" do
381
381
 
382
- let(:hobrecht) do
383
- Address.new(street: "hobrecht")
384
- end
382
+ context "when legacy_pluck_distinct is true" do
383
+ config_override :legacy_pluck_distinct, true
385
384
 
386
- let(:friedel) do
387
- Address.new(street: "friedel")
388
- end
385
+ let(:hobrecht) do
386
+ Address.new(street: "hobrecht")
387
+ end
389
388
 
390
- context "when limiting the result set" do
389
+ let(:friedel) do
390
+ Address.new(street: "friedel")
391
+ end
391
392
 
392
- let(:criteria) do
393
- Address.where(street: "hobrecht").tap do |crit|
394
- crit.documents = [ hobrecht, hobrecht, friedel ]
393
+ context "when limiting the result set" do
394
+
395
+ let(:criteria) do
396
+ Address.where(street: "hobrecht").tap do |crit|
397
+ crit.documents = [ hobrecht, hobrecht, friedel ]
398
+ end
399
+ end
400
+
401
+ let(:context) do
402
+ described_class.new(criteria)
403
+ end
404
+
405
+ it "returns the distinct field values" do
406
+ expect(context.distinct(:street)).to eq([ "hobrecht" ])
395
407
  end
396
408
  end
397
409
 
398
- let(:context) do
399
- described_class.new(criteria)
410
+ context "when not limiting the result set" do
411
+
412
+ let(:criteria) do
413
+ Address.all.tap do |crit|
414
+ crit.documents = [ hobrecht, friedel, friedel ]
415
+ end
416
+ end
417
+
418
+ let(:context) do
419
+ described_class.new(criteria)
420
+ end
421
+
422
+ it "returns the distinct field values" do
423
+ expect(context.distinct(:street)).to eq([ "hobrecht", "friedel" ])
424
+ end
400
425
  end
401
426
 
402
- it "returns the distinct field values" do
403
- expect(context.distinct(:street)).to eq([ "hobrecht" ])
427
+ context 'when there is a collation on the criteria' do
428
+
429
+ let(:criteria) do
430
+ Address.where(street: "hobrecht").tap do |crit|
431
+ crit.documents = [ hobrecht, hobrecht, friedel ]
432
+ end.collation(locale: 'en_US', strength: 2)
433
+ end
434
+
435
+ it "raises an exception" do
436
+ expect {
437
+ context.distinct(:street)
438
+ }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
439
+ end
404
440
  end
405
441
  end
406
442
 
407
- context "when not limiting the result set" do
443
+ context "when legacy_pluck_distinct is false" do
444
+ config_override :legacy_pluck_distinct, false
445
+
446
+ let(:depeche) { Band.create!(name: "Depeche Mode", years: 30, sales: "1E2") }
447
+ let(:new_order) { Band.create!(name: "New Order", years: 25, sales: "2E3") }
448
+ let(:maniacs) { Band.create!(name: "10,000 Maniacs", years: 20, sales: "1E2") }
408
449
 
409
450
  let(:criteria) do
410
- Address.all.tap do |crit|
411
- crit.documents = [ hobrecht, friedel, friedel ]
451
+ Band.all.tap do |crit|
452
+ crit.documents = [ depeche, new_order, maniacs ]
412
453
  end
413
454
  end
414
455
 
@@ -416,23 +457,180 @@ describe Mongoid::Contextual::Memory do
416
457
  described_class.new(criteria)
417
458
  end
418
459
 
419
- it "returns the distinct field values" do
420
- expect(context.distinct(:street)).to eq([ "hobrecht", "friedel" ])
460
+ context "when limiting the result set" do
461
+
462
+ let(:criteria) do
463
+ Band.where(name: "Depeche Mode").tap do |crit|
464
+ crit.documents = [ depeche ]
465
+ end
466
+ end
467
+
468
+ it "returns the distinct matching fields" do
469
+ expect(context.distinct(:name)).to eq([ "Depeche Mode" ])
470
+ end
421
471
  end
422
- end
423
472
 
424
- context 'when there is a collation on the criteria' do
473
+ context "when not limiting the result set" do
425
474
 
426
- let(:criteria) do
427
- Address.where(street: "hobrecht").tap do |crit|
428
- crit.documents = [ hobrecht, hobrecht, friedel ]
429
- end.collation(locale: 'en_US', strength: 2)
475
+ it "returns the distinct field values" do
476
+ expect(context.distinct(:name).sort).to eq([ "10,000 Maniacs", "Depeche Mode", "New Order" ].sort)
477
+ end
430
478
  end
431
479
 
432
- it "raises an exception" do
433
- expect {
434
- context.distinct(:street)
435
- }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
480
+ context "when providing an aliased field" do
481
+
482
+ it "returns the distinct field values" do
483
+ expect(context.distinct(:years).sort).to eq([ 20, 25, 30 ])
484
+ end
485
+ end
486
+
487
+ context "when providing a demongoizable field" do
488
+
489
+ it "returns the non-demongoized distinct field values" do
490
+ expect(context.distinct(:sales).sort).to eq([ BigDecimal("1E2"), BigDecimal("2E3") ])
491
+ end
492
+ end
493
+
494
+ context "when getting a localized field" do
495
+ before do
496
+ I18n.locale = :en
497
+ d = Dictionary.create!(description: 'english-text')
498
+ I18n.locale = :de
499
+ d.description = 'deutsch-text'
500
+ d.save!
501
+ end
502
+
503
+ after do
504
+ I18n.locale = :en
505
+ end
506
+
507
+ let(:criteria) do
508
+ Dictionary.all.tap do |crit|
509
+ crit.documents = [ Dictionary.first ]
510
+ end
511
+ end
512
+
513
+ context "when getting the field without _translations" do
514
+ it "gets the demongoized localized field" do
515
+ expect(context.distinct(:description)).to eq([ 'deutsch-text' ])
516
+ end
517
+ end
518
+
519
+ context "when getting the field with _translations" do
520
+
521
+ it "gets the full hash" do
522
+ expect(context.distinct(:description_translations)).to eq([ { "de" => "deutsch-text", "en" => "english-text" } ])
523
+ end
524
+ end
525
+
526
+ context 'when plucking a specific locale' do
527
+
528
+ let(:distinct) do
529
+ context.distinct(:'description.de')
530
+ end
531
+
532
+ it 'returns the specific translation' do
533
+ expect(distinct).to eq([ "deutsch-text" ])
534
+ end
535
+ end
536
+
537
+ context 'when plucking a specific locale from _translations field' do
538
+
539
+ let(:distinct) do
540
+ context.distinct(:'description_translations.de')
541
+ end
542
+
543
+ it 'returns the specific translations' do
544
+ expect(distinct).to eq(['deutsch-text'])
545
+ end
546
+ end
547
+
548
+ context 'when fallbacks are enabled with a locale list' do
549
+ require_fallbacks
550
+
551
+ around(:all) do |example|
552
+ prev_fallbacks = I18n.fallbacks.dup
553
+ I18n.fallbacks[:he] = [ :en ]
554
+ example.run
555
+ I18n.fallbacks = prev_fallbacks
556
+ end
557
+
558
+ after do
559
+ I18n.locale = :en
560
+ end
561
+
562
+ let(:distinct) do
563
+ context.distinct(:description).first
564
+ end
565
+
566
+ it "correctly uses the fallback" do
567
+ I18n.locale = :en
568
+ d = Dictionary.create!(description: 'english-text')
569
+ I18n.locale = :he
570
+ distinct.should == "english-text"
571
+ end
572
+ end
573
+
574
+ context "when the localized field is embedded" do
575
+ let(:person) do
576
+ p = Passport.new
577
+ I18n.locale = :en
578
+ p.name = "Neil"
579
+ I18n.locale = :he
580
+ p.name = "Nissim"
581
+
582
+ Person.create!(passport: p, employer_id: 12345)
583
+ end
584
+
585
+ after do
586
+ I18n.locale = :en
587
+ end
588
+
589
+ let(:criteria) do
590
+ Person.where(employer_id: 12345).tap do |crit|
591
+ crit.documents = [ person ]
592
+ end
593
+ end
594
+
595
+ let(:distinct) do
596
+ context.distinct("pass.name").first
597
+ end
598
+
599
+ let(:distinct_translations) do
600
+ context.distinct("pass.name_translations").first
601
+ end
602
+
603
+ let(:distinct_translations_field) do
604
+ context.distinct("pass.name_translations.en").first
605
+ end
606
+
607
+ it "returns the translation for the current locale" do
608
+ expect(distinct).to eq("Nissim")
609
+ end
610
+
611
+ it "returns the full _translation hash" do
612
+ expect(distinct_translations).to eq({ "en" => "Neil", "he" => "Nissim" })
613
+ end
614
+
615
+ it "returns the translation for the requested locale" do
616
+ expect(distinct_translations_field).to eq("Neil")
617
+ end
618
+ end
619
+ end
620
+
621
+ context "when getting an embedded field" do
622
+
623
+ let(:label) { Label.new(sales: "1E2") }
624
+ let!(:band) { Band.create!(label: label) }
625
+ let(:criteria) do
626
+ Band.where(_id: band.id).tap do |crit|
627
+ crit.documents = [ band ]
628
+ end
629
+ end
630
+
631
+ it "returns the distinct matching fields" do
632
+ expect(context.distinct("label.sales")).to eq([ BigDecimal("1E2") ])
633
+ end
436
634
  end
437
635
  end
438
636
  end
@@ -624,6 +822,18 @@ describe Mongoid::Contextual::Memory do
624
822
  expect(context.send(method)).to eq(hobrecht)
625
823
  end
626
824
 
825
+ it "returns a list when passing a limit" do
826
+ expect(context.send(method, 2)).to eq([ hobrecht, friedel ])
827
+ end
828
+
829
+ it "returns a list when passing 1" do
830
+ expect(context.send(method, 1)).to eq([ hobrecht ])
831
+ end
832
+
833
+ it "returns the matching document when passing deprecated options" do
834
+ expect(context.send(method, id_sort: :none)).to eq(hobrecht)
835
+ end
836
+
627
837
  context 'when there is a collation on the criteria' do
628
838
 
629
839
  let(:criteria) do
@@ -641,6 +851,108 @@ describe Mongoid::Contextual::Memory do
641
851
  end
642
852
  end
643
853
 
854
+ describe "#take" do
855
+
856
+ let(:hobrecht) do
857
+ Address.new(street: "hobrecht")
858
+ end
859
+
860
+ let(:friedel) do
861
+ Address.new(street: "friedel")
862
+ end
863
+
864
+ let(:criteria) do
865
+ Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit|
866
+ crit.documents = [ hobrecht, friedel ]
867
+ end
868
+ end
869
+
870
+ let(:context) do
871
+ described_class.new(criteria)
872
+ end
873
+
874
+ it "returns the first matching document" do
875
+ expect(context.take).to eq(hobrecht)
876
+ end
877
+
878
+ it "returns an array when passing a limit" do
879
+ expect(context.take(2)).to eq([ hobrecht, friedel ])
880
+ end
881
+
882
+ it "returns an array when passing a limit as 1" do
883
+ expect(context.take(1)).to eq([ hobrecht ])
884
+ end
885
+
886
+ context 'when there is a collation on the criteria' do
887
+
888
+ let(:criteria) do
889
+ Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit|
890
+ crit.documents = [ hobrecht, friedel ]
891
+ end.collation(locale: 'en_US', strength: 2)
892
+ end
893
+
894
+ it "raises an exception" do
895
+ expect {
896
+ context.take
897
+ }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
898
+ end
899
+ end
900
+ end
901
+
902
+ describe "#take!" do
903
+
904
+ let(:hobrecht) do
905
+ Address.new(street: "hobrecht")
906
+ end
907
+
908
+ let(:friedel) do
909
+ Address.new(street: "friedel")
910
+ end
911
+
912
+ let(:criteria) do
913
+ Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit|
914
+ crit.documents = [ hobrecht, friedel ]
915
+ end
916
+ end
917
+
918
+ let(:context) do
919
+ described_class.new(criteria)
920
+ end
921
+
922
+ it "returns the first matching document" do
923
+ expect(context.take!).to eq(hobrecht)
924
+ end
925
+
926
+ context "when the criteria is empty" do
927
+ let(:criteria) do
928
+ Address.where(street: "bogus").tap do |crit|
929
+ crit.documents = []
930
+ end
931
+ end
932
+
933
+ it "raise an error" do
934
+ expect do
935
+ context.take!
936
+ end.to raise_error(Mongoid::Errors::DocumentNotFound, /Could not find a document of class Address./)
937
+ end
938
+ end
939
+
940
+ context 'when there is a collation on the criteria' do
941
+
942
+ let(:criteria) do
943
+ Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit|
944
+ crit.documents = [ hobrecht, friedel ]
945
+ end.collation(locale: 'en_US', strength: 2)
946
+ end
947
+
948
+ it "raises an exception" do
949
+ expect {
950
+ context.take
951
+ }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
952
+ end
953
+ end
954
+ end
955
+
644
956
  describe "#initialize" do
645
957
 
646
958
  context "when the criteria has no options" do
@@ -766,6 +1078,18 @@ describe Mongoid::Contextual::Memory do
766
1078
  expect(context.last).to eq(friedel)
767
1079
  end
768
1080
 
1081
+ it "returns a list when a limit is passed" do
1082
+ expect(context.last(2)).to eq([ hobrecht, friedel ])
1083
+ end
1084
+
1085
+ it "returns a list when the limit is 1" do
1086
+ expect(context.last(1)).to eq([ friedel ])
1087
+ end
1088
+
1089
+ it "returns the matching document when passing deprecated options" do
1090
+ expect(context.last(id_sort: :none)).to eq(friedel)
1091
+ end
1092
+
769
1093
  context 'when there is a collation on the criteria' do
770
1094
 
771
1095
  let(:criteria) do
@@ -919,72 +1243,509 @@ describe Mongoid::Contextual::Memory do
919
1243
 
920
1244
  describe "#pluck" do
921
1245
 
922
- let(:hobrecht) do
923
- Address.new(street: "hobrecht")
1246
+ let(:context) do
1247
+ described_class.new(criteria)
924
1248
  end
925
1249
 
926
- let(:friedel) do
927
- Address.new(street: "friedel")
928
- end
1250
+ context "when legacy_pluck_distinct is true" do
1251
+ config_override :legacy_pluck_distinct, true
929
1252
 
930
- let(:criteria) do
931
- Address.all.tap do |crit|
932
- crit.documents = [ hobrecht, friedel ]
1253
+ let(:hobrecht) do
1254
+ Address.new(street: "hobrecht")
933
1255
  end
934
- end
935
1256
 
936
- let(:context) do
937
- described_class.new(criteria)
938
- end
1257
+ let(:friedel) do
1258
+ Address.new(street: "friedel")
1259
+ end
1260
+
1261
+ let(:criteria) do
1262
+ Address.all.tap do |crit|
1263
+ crit.documents = [ hobrecht, friedel ]
1264
+ end
1265
+ end
1266
+
1267
+ context "when plucking" do
1268
+
1269
+ let!(:plucked) do
1270
+ context.pluck(:street)
1271
+ end
1272
+
1273
+ it "returns the values" do
1274
+ expect(plucked).to eq([ "hobrecht", "friedel" ])
1275
+ end
1276
+ end
1277
+
1278
+ context "when plucking a mix of empty and non-empty values" do
1279
+
1280
+ let(:empty_doc) do
1281
+ Address.new(street: nil)
1282
+ end
1283
+
1284
+ let(:criteria) do
1285
+ Address.all.tap do |crit|
1286
+ crit.documents = [ hobrecht, friedel, empty_doc ]
1287
+ end
1288
+ end
1289
+
1290
+ let!(:plucked) do
1291
+ context.pluck(:street)
1292
+ end
1293
+
1294
+ it "returns the values" do
1295
+ expect(plucked).to eq([ "hobrecht", "friedel", nil ])
1296
+ end
1297
+ end
1298
+
1299
+ context "when plucking a field that doesnt exist" do
1300
+
1301
+ context "when pluck one field" do
1302
+
1303
+ let(:plucked) do
1304
+ context.pluck(:foo)
1305
+ end
1306
+
1307
+ it "returns a empty array" do
1308
+ expect(plucked).to eq([nil, nil])
1309
+ end
1310
+ end
939
1311
 
940
- context "when plucking" do
1312
+ context "when pluck multiple fields" do
941
1313
 
942
- let!(:plucked) do
943
- context.pluck(:street)
1314
+ let(:plucked) do
1315
+ context.pluck(:foo, :bar)
1316
+ end
1317
+
1318
+ it "returns a empty array" do
1319
+ expect(plucked).to eq([[nil, nil], [nil, nil]])
1320
+ end
1321
+ end
944
1322
  end
945
1323
 
946
- it "returns the values" do
947
- expect(plucked).to eq([ "hobrecht", "friedel" ])
1324
+ context 'when there is a collation on the criteria' do
1325
+
1326
+ let(:criteria) do
1327
+ Address.all.tap do |crit|
1328
+ crit.documents = [ hobrecht, friedel ]
1329
+ end.collation(locale: 'en_US', strength: 2)
1330
+ end
1331
+
1332
+ it "raises an exception" do
1333
+ expect {
1334
+ context.pluck(:foo, :bar)
1335
+ }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
1336
+ end
948
1337
  end
949
1338
  end
950
1339
 
951
- context "when plucking a field that doesnt exist" do
1340
+ context "when legacy_pluck_distinct is false" do
1341
+ config_override :legacy_pluck_distinct, false
952
1342
 
953
- context "when pluck one field" do
1343
+ let!(:depeche) do
1344
+ Band.create!(name: "Depeche Mode", likes: 3)
1345
+ end
1346
+
1347
+ let!(:tool) do
1348
+ Band.create!(name: "Tool", likes: 3)
1349
+ end
1350
+
1351
+ let!(:photek) do
1352
+ Band.create!(name: "Photek", likes: 1)
1353
+ end
1354
+
1355
+ let(:maniacs) do
1356
+ Band.create!(name: "10,000 Maniacs", likes: 1, sales: "1E2")
1357
+ end
1358
+
1359
+ let(:criteria) do
1360
+ Band.all.tap do |crit|
1361
+ crit.documents = [ depeche, tool, photek, maniacs ]
1362
+ end
1363
+ end
1364
+
1365
+ context "when the field is aliased" do
1366
+
1367
+ let!(:expensive) do
1368
+ Product.create!(price: 100000)
1369
+ end
1370
+
1371
+ let!(:cheap) do
1372
+ Product.create!(price: 1)
1373
+ end
1374
+
1375
+ let(:criteria) do
1376
+ Product.all.tap do |crit|
1377
+ crit.documents = [ expensive, cheap ]
1378
+ end
1379
+ end
1380
+
1381
+ context "when using alias_attribute" do
1382
+
1383
+ let(:plucked) do
1384
+ context.pluck(:price)
1385
+ end
1386
+
1387
+ it "uses the aliases" do
1388
+ expect(plucked).to eq([ 100000, 1 ])
1389
+ end
1390
+ end
1391
+ end
1392
+
1393
+ context "when the criteria matches" do
1394
+
1395
+ context "when there are no duplicate values" do
1396
+
1397
+ let!(:plucked) do
1398
+ context.pluck(:name)
1399
+ end
1400
+
1401
+ it "returns the values" do
1402
+ expect(plucked).to contain_exactly("10,000 Maniacs", "Depeche Mode", "Tool", "Photek")
1403
+ end
1404
+
1405
+ context "when subsequently executing the criteria without a pluck" do
1406
+
1407
+ it "does not limit the fields" do
1408
+ expect(context.first.likes).to eq(3)
1409
+ end
1410
+ end
1411
+
1412
+ context 'when the field is a subdocument' do
1413
+
1414
+ context 'when a top-level field and a subdocument field are plucked' do
1415
+ let(:criteria) do
1416
+ Band.where(name: 'FKA Twigs').tap do |crit|
1417
+ crit.documents = [
1418
+ Band.create!(name: 'FKA Twigs'),
1419
+ Band.create!(name: 'FKA Twigs', records: [ Record.new(name: 'LP1') ])
1420
+ ]
1421
+ end
1422
+ end
1423
+
1424
+ let(:embedded_pluck) do
1425
+ context.pluck(:name, 'records.name')
1426
+ end
1427
+
1428
+ let(:expected) do
1429
+ [
1430
+ ["FKA Twigs", []],
1431
+ ['FKA Twigs', ["LP1"]]
1432
+ ]
1433
+ end
1434
+
1435
+ it 'returns the list of top-level field and subdocument values' do
1436
+ expect(embedded_pluck).to eq(expected)
1437
+ end
1438
+ end
1439
+
1440
+ context 'when only a subdocument field is plucked' do
1441
+
1442
+ let(:criteria) do
1443
+ Band.where(name: 'FKA Twigs').tap do |crit|
1444
+ crit.documents = [
1445
+ Band.create!(name: 'FKA Twigs'),
1446
+ Band.create!(name: 'FKA Twigs', records: [ Record.new(name: 'LP1') ])
1447
+ ]
1448
+ end
1449
+ end
1450
+
1451
+ let(:embedded_pluck) do
1452
+ context.pluck('records.name')
1453
+ end
1454
+
1455
+ let(:expected) do
1456
+ [
1457
+ [],
1458
+ ["LP1"]
1459
+ ]
1460
+ end
1461
+
1462
+ it 'returns the list of subdocument values' do
1463
+ expect(embedded_pluck).to eq(expected)
1464
+ end
1465
+ end
1466
+ end
1467
+ end
1468
+
1469
+ context "when plucking multi-fields" do
1470
+
1471
+ let(:plucked) do
1472
+ context.pluck(:name, :likes)
1473
+ end
1474
+
1475
+ it "returns the values" do
1476
+ expect(plucked).to contain_exactly(["10,000 Maniacs", 1], ["Depeche Mode", 3], ["Tool", 3], ["Photek", 1])
1477
+ end
1478
+ end
1479
+
1480
+ context "when there are duplicate values" do
1481
+
1482
+ let(:plucked) do
1483
+ context.pluck(:likes)
1484
+ end
1485
+
1486
+ it "returns the duplicates" do
1487
+ expect(plucked).to contain_exactly(1, 3, 3, 1)
1488
+ end
1489
+ end
1490
+ end
1491
+
1492
+ context "when plucking an aliased field" do
954
1493
 
955
1494
  let(:plucked) do
956
- context.pluck(:foo)
1495
+ context.pluck(:id)
957
1496
  end
958
1497
 
959
- it "returns a empty array" do
960
- expect(plucked).to eq([])
1498
+ it "returns the field values" do
1499
+ expect(plucked).to eq([ depeche.id, tool.id, photek.id, maniacs.id ])
961
1500
  end
962
1501
  end
963
1502
 
964
- context "when pluck multiple fields" do
1503
+ context "when plucking existent and non-existent fields" do
965
1504
 
966
1505
  let(:plucked) do
967
- context.pluck(:foo, :bar)
1506
+ context.pluck(:id, :fooz)
968
1507
  end
969
1508
 
970
- it "returns a empty array" do
971
- expect(plucked).to eq([[], []])
1509
+ it "returns nil for the field that doesnt exist" do
1510
+ expect(plucked).to eq([[depeche.id, nil], [tool.id, nil], [photek.id, nil], [maniacs.id, nil] ])
972
1511
  end
973
1512
  end
974
- end
975
1513
 
976
- context 'when there is a collation on the criteria' do
1514
+ context "when plucking a field that doesnt exist" do
977
1515
 
978
- let(:criteria) do
979
- Address.all.tap do |crit|
980
- crit.documents = [ hobrecht, friedel ]
981
- end.collation(locale: 'en_US', strength: 2)
1516
+ context "when pluck one field" do
1517
+
1518
+ let(:plucked) do
1519
+ context.pluck(:foo)
1520
+ end
1521
+
1522
+ it "returns an array with nil values" do
1523
+ expect(plucked).to eq([nil, nil, nil, nil])
1524
+ end
1525
+ end
1526
+
1527
+ context "when pluck multiple fields" do
1528
+
1529
+ let(:plucked) do
1530
+ context.pluck(:foo, :bar)
1531
+ end
1532
+
1533
+ it "returns an array of arrays with nil values" do
1534
+ expect(plucked).to eq([[nil, nil], [nil, nil], [nil, nil], [nil, nil]])
1535
+ end
1536
+ end
982
1537
  end
983
1538
 
984
- it "raises an exception" do
985
- expect {
986
- context.pluck(:foo, :bar)
987
- }.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
1539
+ context 'when plucking a localized field' do
1540
+
1541
+ before do
1542
+ I18n.locale = :en
1543
+ d = Dictionary.create!(description: 'english-text')
1544
+ I18n.locale = :de
1545
+ d.description = 'deutsch-text'
1546
+ d.save!
1547
+ end
1548
+
1549
+ after do
1550
+ I18n.locale = :en
1551
+ end
1552
+
1553
+ let(:criteria) do
1554
+ Dictionary.all.tap do |crit|
1555
+ crit.documents = [ Dictionary.first ]
1556
+ end
1557
+ end
1558
+
1559
+ context 'when plucking the entire field' do
1560
+
1561
+ let(:plucked) do
1562
+ context.pluck(:description)
1563
+ end
1564
+
1565
+ let(:plucked_translations) do
1566
+ context.pluck(:description_translations)
1567
+ end
1568
+
1569
+ let(:plucked_translations_both) do
1570
+ context.pluck(:description_translations, :description)
1571
+ end
1572
+
1573
+ it 'returns the demongoized translations' do
1574
+ expect(plucked.first).to eq('deutsch-text')
1575
+ end
1576
+
1577
+ it 'returns the full translations hash to _translations' do
1578
+ expect(plucked_translations.first).to eq({"de"=>"deutsch-text", "en"=>"english-text"})
1579
+ end
1580
+
1581
+ it 'returns both' do
1582
+ expect(plucked_translations_both.first).to eq([{"de"=>"deutsch-text", "en"=>"english-text"}, "deutsch-text"])
1583
+ end
1584
+ end
1585
+
1586
+ context 'when plucking a specific locale' do
1587
+
1588
+ let(:plucked) do
1589
+ context.pluck(:'description.de')
1590
+ end
1591
+
1592
+ it 'returns the specific translations' do
1593
+ expect(plucked.first).to eq('deutsch-text')
1594
+ end
1595
+ end
1596
+
1597
+ context 'when plucking a specific locale from _translations field' do
1598
+
1599
+ let(:plucked) do
1600
+ context.pluck(:'description_translations.de')
1601
+ end
1602
+
1603
+ it 'returns the specific translations' do
1604
+ expect(plucked.first).to eq('deutsch-text')
1605
+ end
1606
+ end
1607
+
1608
+ context 'when fallbacks are enabled with a locale list' do
1609
+ require_fallbacks
1610
+
1611
+ around(:all) do |example|
1612
+ prev_fallbacks = I18n.fallbacks.dup
1613
+ I18n.fallbacks[:he] = [ :en ]
1614
+ example.run
1615
+ I18n.fallbacks = prev_fallbacks
1616
+ end
1617
+
1618
+ after do
1619
+ I18n.locale = :en
1620
+ end
1621
+
1622
+ let(:plucked) do
1623
+ context.pluck(:description).first
1624
+ end
1625
+
1626
+ it "correctly uses the fallback" do
1627
+ I18n.locale = :en
1628
+ d = Dictionary.create!(description: 'english-text')
1629
+ I18n.locale = :he
1630
+ plucked.should == "english-text"
1631
+ end
1632
+ end
1633
+
1634
+ context "when the localized field is embedded" do
1635
+ before do
1636
+ p = Passport.new
1637
+ I18n.locale = :en
1638
+ p.name = "Neil"
1639
+ I18n.locale = :he
1640
+ p.name = "Nissim"
1641
+
1642
+ Person.create!(passport: p, employer_id: 12345)
1643
+ end
1644
+
1645
+ after do
1646
+ I18n.locale = :en
1647
+ end
1648
+
1649
+ let(:plucked) do
1650
+ Person.where(employer_id: 12345).pluck("pass.name").first
1651
+ end
1652
+
1653
+ let(:plucked_translations) do
1654
+ Person.where(employer_id: 12345).pluck("pass.name_translations").first
1655
+ end
1656
+
1657
+ let(:plucked_translations_field) do
1658
+ Person.where(employer_id: 12345).pluck("pass.name_translations.en").first
1659
+ end
1660
+
1661
+ it "returns the translation for the current locale" do
1662
+ expect(plucked).to eq("Nissim")
1663
+ end
1664
+
1665
+ it "returns the full _translation hash" do
1666
+ expect(plucked_translations).to eq({ "en" => "Neil", "he" => "Nissim" })
1667
+ end
1668
+
1669
+ it "returns the translation for the requested locale" do
1670
+ expect(plucked_translations_field).to eq("Neil")
1671
+ end
1672
+ end
1673
+ end
1674
+
1675
+ context 'when plucking a field to be demongoized' do
1676
+
1677
+ let(:plucked) do
1678
+ Band.where(name: maniacs.name).pluck(:sales)
1679
+ end
1680
+
1681
+ it "demongoizes the field" do
1682
+ expect(plucked.first).to be_a(BigDecimal)
1683
+ expect(plucked.first).to eq(BigDecimal("1E2"))
1684
+ end
1685
+ end
1686
+
1687
+ context "when plucking an embedded field" do
1688
+ let(:label) { Label.new(sales: "1E2") }
1689
+ let!(:band) { Band.create!(label: label) }
1690
+
1691
+ let(:plucked) do
1692
+ Band.where(_id: band.id).tap do |crit|
1693
+ crit.documents = [band]
1694
+ end.pluck("label.sales")
1695
+ end
1696
+
1697
+ it "demongoizes the field" do
1698
+ expect(plucked).to eq([ BigDecimal("1E2") ])
1699
+ end
1700
+ end
1701
+
1702
+ context "when plucking an embeds_many field" do
1703
+ let(:label) { Label.new(sales: "1E2") }
1704
+ let!(:band) { Band.create!(labels: [label]) }
1705
+
1706
+ let(:plucked) { Band.where(_id: band.id).pluck("labels.sales") }
1707
+
1708
+ it "demongoizes the field" do
1709
+ expect(plucked.first).to eq([ BigDecimal("1E2") ])
1710
+ end
1711
+ end
1712
+
1713
+ context "when plucking a nonexistent embedded field" do
1714
+ let(:label) { Label.new(sales: "1E2") }
1715
+ let!(:band) { Band.create!(label: label) }
1716
+
1717
+ let(:plucked) do
1718
+ Band.where(_id: band.id).tap do |crit|
1719
+ crit.documents = band
1720
+ end.pluck("label.qwerty")
1721
+ end
1722
+
1723
+ it "returns nil" do
1724
+ expect(plucked.first).to eq(nil)
1725
+ end
1726
+ end
1727
+
1728
+ context "when tallying deeply nested arrays/embedded associations" do
1729
+
1730
+ let(:criteria) do
1731
+ Person.all.tap do |crit|
1732
+ crit.documents = [
1733
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ]),
1734
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 2 } } ]))) ]),
1735
+ Person.create!(addresses: [ Address.new(code: Code.new(deepest: Deepest.new(array: [ { y: { z: 1 } }, { y: { z: 3 } } ]))) ]),
1736
+ ]
1737
+ end
1738
+ end
1739
+
1740
+ let(:plucked) do
1741
+ context.pluck("addresses.code.deepest.array.y.z")
1742
+ end
1743
+
1744
+ it "returns the correct hash" do
1745
+ expect(plucked).to eq([
1746
+ [ [ 1, 2 ] ], [ [ 1, 2 ] ], [ [ 1, 3 ] ]
1747
+ ])
1748
+ end
988
1749
  end
989
1750
  end
990
1751
  end