mongoid 7.1.0.rc0 → 7.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 (40) 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 +4 -4
  6. data/lib/mongoid.rb +3 -2
  7. data/lib/mongoid/association/many.rb +3 -2
  8. data/lib/mongoid/association/proxy.rb +5 -3
  9. data/lib/mongoid/association/referenced/has_many/enumerable.rb +2 -22
  10. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -2
  11. data/lib/mongoid/attributes.rb +28 -20
  12. data/lib/mongoid/config.rb +3 -3
  13. data/lib/mongoid/config/options.rb +5 -2
  14. data/lib/mongoid/contextual.rb +5 -4
  15. data/lib/mongoid/contextual/geo_near.rb +3 -2
  16. data/lib/mongoid/contextual/map_reduce.rb +3 -2
  17. data/lib/mongoid/contextual/mongo.rb +2 -1
  18. data/lib/mongoid/fields/standard.rb +2 -1
  19. data/lib/mongoid/findable.rb +5 -4
  20. data/lib/mongoid/interceptable.rb +5 -1
  21. data/lib/mongoid/railties/database.rake +7 -0
  22. data/lib/mongoid/serializable.rb +3 -1
  23. data/lib/mongoid/shardable.rb +56 -4
  24. data/lib/mongoid/tasks/database.rake +10 -5
  25. data/lib/mongoid/tasks/database.rb +48 -0
  26. data/lib/mongoid/timestamps/timeless.rb +3 -1
  27. data/lib/mongoid/version.rb +1 -1
  28. data/spec/integration/shardable_spec.rb +133 -0
  29. data/spec/lite_spec_helper.rb +3 -0
  30. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +41 -68
  31. data/spec/mongoid/attributes_spec.rb +19 -7
  32. data/spec/mongoid/changeable_spec.rb +23 -0
  33. data/spec/mongoid/document_fields_spec.rb +29 -0
  34. data/spec/mongoid/interceptable_spec.rb +62 -0
  35. data/spec/mongoid/interceptable_spec_models.rb +76 -0
  36. data/spec/mongoid/shardable_models.rb +61 -0
  37. data/spec/mongoid/shardable_spec.rb +69 -16
  38. data/spec/support/lite_constraints.rb +22 -0
  39. metadata +20 -12
  40. metadata.gz.sig +0 -0
@@ -5,27 +5,32 @@ namespace :db do
5
5
  task :load_models do
6
6
  end
7
7
 
8
- desc "Create the indexes defined on your mongoid models"
8
+ desc "Create indexes specified in Mongoid models"
9
9
  task :create_indexes => [:environment, :load_models] do
10
10
  ::Mongoid::Tasks::Database.create_indexes
11
11
  end
12
12
 
13
- desc "Remove indexes that exist in the database but aren't specified on the models"
13
+ desc "Remove indexes that exist in the database but are not specified in Mongoid models"
14
14
  task :remove_undefined_indexes => [:environment, :load_models] do
15
15
  ::Mongoid::Tasks::Database.remove_undefined_indexes
16
16
  end
17
17
 
18
- desc "Remove the indexes defined on your mongoid models without questions!"
18
+ desc "Remove indexes specified in Mongoid models"
19
19
  task :remove_indexes => [:environment, :load_models] do
20
20
  ::Mongoid::Tasks::Database.remove_indexes
21
21
  end
22
22
 
23
- desc "Drops the default client database"
23
+ desc "Shard collections with shard keys specified in Mongoid models"
24
+ task :shard_collections => [:environment, :load_models] do
25
+ ::Mongoid::Tasks::Database.shard_collections
26
+ end
27
+
28
+ desc "Drop the database of the default Mongoid client"
24
29
  task :drop => :environment do
25
30
  ::Mongoid::Clients.default.database.drop
26
31
  end
27
32
 
28
- desc "Drop all collections except the system collections"
33
+ desc "Drop all non-system collections"
29
34
  task :purge => :environment do
30
35
  ::Mongoid.purge!
31
36
  end
@@ -108,6 +108,54 @@ module Mongoid
108
108
  end.compact
109
109
  end
110
110
 
111
+ # Shard collections for models that declare shard keys.
112
+ #
113
+ # Returns the model classes that have had their collections sharded,
114
+ # including model classes whose collections had already been sharded
115
+ # prior to the invocation of this method.
116
+ #
117
+ # @example Shard all collections
118
+ # Mongoid::Tasks::Database.shard_collections
119
+ #
120
+ # @return [ Array<Class> ] The sharded models
121
+ def shard_collections(models = ::Mongoid.models)
122
+ models.map do |model|
123
+ next if model.shard_config.nil?
124
+
125
+ if model.embedded? && !model.cyclic?
126
+ logger.warn("MONGOID: #{model} has shard config but is emdedded")
127
+ next
128
+ end
129
+
130
+ unless model.collection.cluster.sharded?
131
+ logger.warn("MONGOID: #{model} has shard config but is not persisted in a sharded cluster: #{model.collection.cluster.summary}")
132
+ next
133
+ end
134
+
135
+ # Database must exist in order to run collStats
136
+ model.collection.create
137
+
138
+ stats = model.collection.database.command(collStats: model.collection.name).first
139
+ if stats[:sharded]
140
+ logger.info("MONGOID: #{model.collection.namespace} is already sharded for #{model}")
141
+ next model
142
+ end
143
+
144
+ admin_db = model.collection.client.use(:admin).database
145
+ admin_db.command(enableSharding: model.collection.database.name)
146
+ begin
147
+ admin_db.command(shardCollection: model.collection.namespace, **model.shard_config)
148
+ rescue Mongo::Error::OperationFailure => e
149
+ logger.error("MONGOID: Failed to shard collection #{model.collection.namespace} for #{model}: #{e.class}: #{e}")
150
+ next
151
+ end
152
+
153
+ logger.info("MONGOID: Sharded collection #{model.collection.namespace} for #{model}")
154
+
155
+ model
156
+ end.compact
157
+ end
158
+
111
159
  private
112
160
 
113
161
  def logger
@@ -44,11 +44,13 @@ module Mongoid
44
44
  end
45
45
 
46
46
  class << self
47
+ extend Forwardable
47
48
 
48
49
  def timeless_table
49
50
  Thread.current['[mongoid]:timeless'] ||= Hash.new
50
51
  end
51
- delegate :[]=, :[], to: :timeless_table
52
+
53
+ def_delegators :timeless_table, :[]=, :[]
52
54
  end
53
55
 
54
56
  private
@@ -2,5 +2,5 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  module Mongoid
5
- VERSION = "7.1.0.rc0"
5
+ VERSION = "7.1.0"
6
6
  end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+ require_relative '../mongoid/shardable_models'
6
+
7
+ describe 'Sharding helpers' do
8
+ require_topology :sharded
9
+
10
+ describe 'shard_collection rake task' do
11
+ let(:shard_collections) do
12
+ Mongoid::Tasks::Database.shard_collections([model_cls])
13
+ end
14
+
15
+ let(:create_indexes) do
16
+ Mongoid::Tasks::Database.create_indexes([model_cls])
17
+ end
18
+
19
+ shared_examples_for 'shards collection' do
20
+ it 'returns the model class' do
21
+ shard_collections.should == [model_cls]
22
+ end
23
+
24
+ it 'shards collection' do
25
+ shard_collections
26
+
27
+ stats = model_cls.collection.database.command(collStats: model_cls.collection.name).first
28
+ stats[:sharded].should be true
29
+ end
30
+ end
31
+
32
+ context 'simple model' do
33
+ let(:model_cls) { SmMovie }
34
+
35
+ before do
36
+ SmMovie.collection.drop
37
+ end
38
+
39
+ it_behaves_like 'shards collection'
40
+
41
+ it 'makes inserts work' do
42
+ shard_collections
43
+ create_indexes
44
+
45
+ SmMovie.create!(year: 2020)
46
+ end
47
+ end
48
+
49
+ context 'when database does not exist' do
50
+ let(:model_cls) { SmMovie }
51
+
52
+ before do
53
+ SmMovie.collection.database.drop
54
+ end
55
+
56
+ it_behaves_like 'shards collection'
57
+ end
58
+
59
+ context 'when database exists but collection does not exist' do
60
+ let(:model_cls) { SmMovie }
61
+
62
+ before do
63
+ SmMovie.collection.database.drop
64
+ SmMovie.collection.create
65
+ SmMovie.collection.drop
66
+ end
67
+
68
+ it_behaves_like 'shards collection'
69
+ end
70
+
71
+ context 'when collection is already sharded' do
72
+ let(:model_cls) { SmMovie }
73
+
74
+ before do
75
+ Mongoid::Tasks::Database.shard_collections([model_cls])
76
+ end
77
+
78
+ it_behaves_like 'shards collection'
79
+ end
80
+
81
+ context 'when hashed shard key is given' do
82
+ let(:model_cls) { SmAssistant }
83
+
84
+ before do
85
+ SmAssistant.collection.drop
86
+ end
87
+
88
+ it_behaves_like 'shards collection'
89
+
90
+ it 'makes inserts work' do
91
+ shard_collections
92
+ create_indexes
93
+
94
+ SmAssistant.create!(gender: 'm')
95
+ end
96
+ end
97
+
98
+ context 'when shard configuration is invalid' do
99
+ let(:model_cls) { SmActor }
100
+
101
+ it 'returns empty array' do
102
+ shard_collections.should == []
103
+ end
104
+
105
+ it 'does not shards collection' do
106
+ shard_collections
107
+
108
+ stats = model_cls.collection.database.command(collStats: model_cls.collection.name).first
109
+ stats[:sharded].should be false
110
+ end
111
+
112
+ end
113
+
114
+ context 'when models have no sharded configuration' do
115
+ let(:model_cls) { SmNotSharded }
116
+
117
+ before do
118
+ model_cls.shard_config.should be nil
119
+ end
120
+
121
+ it 'returns empty array' do
122
+ shard_collections.should == []
123
+ end
124
+
125
+ it 'does not shards collection' do
126
+ shard_collections
127
+
128
+ stats = model_cls.collection.database.command(collStats: model_cls.collection.name).first
129
+ stats[:sharded].should be false
130
+ end
131
+ end
132
+ end
133
+ end
@@ -15,6 +15,7 @@ require "mongo"
15
15
  require 'pp'
16
16
 
17
17
  require 'support/spec_config'
18
+ require 'support/lite_constraints'
18
19
 
19
20
  unless SpecConfig.instance.ci?
20
21
  begin
@@ -58,6 +59,8 @@ RSpec.configure do |config|
58
59
  end
59
60
  end
60
61
  end
62
+
63
+ config.extend(LiteConstraints)
61
64
  end
62
65
 
63
66
  # require all shared examples
@@ -782,11 +782,7 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
782
782
  end
783
783
  end
784
784
 
785
- shared_examples 'first or one' do
786
-
787
- subject do
788
- enumerable.public_send(subject_method)
789
- end
785
+ describe "#first" do
790
786
 
791
787
  let(:person) do
792
788
  Person.create
@@ -810,8 +806,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
810
806
  Post.create(person_id: person.id)
811
807
  end
812
808
 
809
+ let(:first) do
810
+ enumerable.first
811
+ end
812
+
813
813
  it "returns the first unloaded doc" do
814
- expect(subject).to eq(post)
814
+ expect(first).to eq(post)
815
815
  end
816
816
 
817
817
  it "does not load the enumerable" do
@@ -820,7 +820,7 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
820
820
 
821
821
  it "receives query only once" do
822
822
  expect(criteria).to receive(:first).once
823
- subject
823
+ first
824
824
  end
825
825
  end
826
826
 
@@ -838,10 +838,14 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
838
838
  enumerable << post_two
839
839
  end
840
840
 
841
+ let(:first) do
842
+ enumerable.first
843
+ end
844
+
841
845
  context "when a perviously persisted unloaded doc exists" do
842
846
 
843
847
  it "returns the first added doc" do
844
- expect(subject).to eq(post)
848
+ expect(first).to eq(post)
845
849
  end
846
850
 
847
851
  it "does not load the enumerable" do
@@ -861,8 +865,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
861
865
  enumerable << post
862
866
  end
863
867
 
868
+ let(:first) do
869
+ enumerable.first
870
+ end
871
+
864
872
  it "returns the first loaded doc" do
865
- expect(subject).to eq(post)
873
+ expect(first).to eq(post)
866
874
  end
867
875
 
868
876
  it "does not load the enumerable" do
@@ -872,8 +880,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
872
880
 
873
881
  context "when unloaded and added are empty" do
874
882
 
883
+ let(:first) do
884
+ enumerable.first
885
+ end
886
+
875
887
  it "returns nil" do
876
- expect(subject).to be_nil
888
+ expect(first).to be_nil
877
889
  end
878
890
 
879
891
  it "does not load the enumerable" do
@@ -894,8 +906,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
894
906
  described_class.new([ post ])
895
907
  end
896
908
 
909
+ let(:first) do
910
+ enumerable.first
911
+ end
912
+
897
913
  it "returns the first loaded doc" do
898
- expect(subject).to eq(post)
914
+ expect(first).to eq(post)
899
915
  end
900
916
  end
901
917
 
@@ -913,8 +929,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
913
929
  enumerable << post
914
930
  end
915
931
 
932
+ let(:first) do
933
+ enumerable.first
934
+ end
935
+
916
936
  it "returns the first added doc" do
917
- expect(subject).to eq(post)
937
+ expect(first).to eq(post)
918
938
  end
919
939
  end
920
940
 
@@ -924,8 +944,12 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
924
944
  described_class.new([])
925
945
  end
926
946
 
947
+ let(:first) do
948
+ enumerable.first
949
+ end
950
+
927
951
  it "returns nil" do
928
- expect(subject).to be_nil
952
+ expect(first).to be_nil
929
953
  end
930
954
  end
931
955
  end
@@ -953,21 +977,9 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
953
977
  end
954
978
 
955
979
  it 'does not use the sort on id' do
956
- expect(enumerable.send(subject_method, id_sort: :none)).to eq(first_post)
980
+ expect(enumerable.first(id_sort: :none)).to eq(first_post)
957
981
  end
958
982
  end
959
- end
960
-
961
- describe "#first" do
962
- let(:subject_method) do
963
- :first
964
- end
965
-
966
- it_behaves_like 'first or one'
967
-
968
- subject do
969
- enumerable.public_send(subject_method)
970
- end
971
983
 
972
984
  context 'when the id_sort option is not provided' do
973
985
 
@@ -984,54 +996,15 @@ describe Mongoid::Association::Referenced::HasMany::Targets::Enumerable do
984
996
  end
985
997
 
986
998
  let!(:first_post) do
987
- person.posts.create(id: 'one', title: "One")
999
+ person.posts.create(title: "One")
988
1000
  end
989
1001
 
990
1002
  let!(:second_post) do
991
- person.posts.create(id: 'atwo', title: "Two")
1003
+ person.posts.create(title: "Two")
992
1004
  end
993
1005
 
994
1006
  it 'uses the sort on id' do
995
- expect(subject).to eq(second_post)
996
- end
997
- end
998
- end
999
-
1000
- describe "#one" do
1001
- let(:subject_method) do
1002
- :one
1003
- end
1004
-
1005
- it_behaves_like 'first or one'
1006
-
1007
- subject do
1008
- enumerable.public_send(subject_method)
1009
- end
1010
-
1011
- context 'when the id_sort option is not provided' do
1012
-
1013
- let(:person) do
1014
- Person.create
1015
- end
1016
-
1017
- let(:criteria) do
1018
- Post.where(person_id: person.id)
1019
- end
1020
-
1021
- let(:enumerable) do
1022
- described_class.new(criteria)
1023
- end
1024
-
1025
- let!(:first_post) do
1026
- person.posts.create(id: 'one', title: "One")
1027
- end
1028
-
1029
- let!(:second_post) do
1030
- person.posts.create(id: 'atwo', title: "Two")
1031
- end
1032
-
1033
- it 'does not use the sort on id' do
1034
- expect(subject).to eq(first_post)
1007
+ expect(enumerable.first).to eq(first_post)
1035
1008
  end
1036
1009
  end
1037
1010
  end