mongoid 6.3.0 → 6.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Rakefile +12 -0
  5. data/lib/config/locales/en.yml +21 -0
  6. data/lib/mongoid.rb +2 -2
  7. data/lib/mongoid/clients.rb +2 -0
  8. data/lib/mongoid/clients/sessions.rb +113 -0
  9. data/lib/mongoid/clients/storage_options.rb +1 -0
  10. data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
  11. data/lib/mongoid/contextual/map_reduce.rb +7 -3
  12. data/lib/mongoid/contextual/memory.rb +7 -2
  13. data/lib/mongoid/contextual/mongo.rb +11 -2
  14. data/lib/mongoid/criteria.rb +1 -0
  15. data/lib/mongoid/criteria/modifiable.rb +12 -2
  16. data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
  17. data/lib/mongoid/criteria/queryable/selectable.rb +34 -7
  18. data/lib/mongoid/document.rb +4 -4
  19. data/lib/mongoid/errors.rb +1 -0
  20. data/lib/mongoid/errors/invalid_session_use.rb +24 -0
  21. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  22. data/lib/mongoid/extensions/regexp.rb +1 -0
  23. data/lib/mongoid/extensions/string.rb +3 -1
  24. data/lib/mongoid/indexable.rb +4 -4
  25. data/lib/mongoid/matchable.rb +3 -0
  26. data/lib/mongoid/matchable/nor.rb +37 -0
  27. data/lib/mongoid/persistable.rb +1 -1
  28. data/lib/mongoid/persistable/creatable.rb +4 -2
  29. data/lib/mongoid/persistable/deletable.rb +4 -2
  30. data/lib/mongoid/persistable/destroyable.rb +1 -5
  31. data/lib/mongoid/persistable/settable.rb +5 -5
  32. data/lib/mongoid/persistable/updatable.rb +2 -2
  33. data/lib/mongoid/persistable/upsertable.rb +2 -1
  34. data/lib/mongoid/persistence_context.rb +4 -0
  35. data/lib/mongoid/railtie.rb +17 -0
  36. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  37. data/lib/mongoid/relations/embedded/batchable.rb +10 -4
  38. data/lib/mongoid/relations/embedded/many.rb +23 -0
  39. data/lib/mongoid/relations/many.rb +4 -0
  40. data/lib/mongoid/relations/referenced/many.rb +1 -1
  41. data/lib/mongoid/relations/touchable.rb +1 -1
  42. data/lib/mongoid/reloadable.rb +1 -1
  43. data/lib/mongoid/scopable.rb +3 -3
  44. data/lib/mongoid/tasks/database.rb +3 -2
  45. data/lib/mongoid/threaded.rb +74 -0
  46. data/lib/mongoid/version.rb +1 -1
  47. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -0
  48. data/spec/app/models/array_field.rb +7 -0
  49. data/spec/app/models/delegating_patient.rb +16 -0
  50. data/spec/integration/document_spec.rb +22 -0
  51. data/spec/mongoid/attributes/nested_spec.rb +4 -0
  52. data/spec/mongoid/clients/factory_spec.rb +52 -28
  53. data/spec/mongoid/clients/options_spec.rb +30 -15
  54. data/spec/mongoid/clients/sessions_spec.rb +334 -0
  55. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  56. data/spec/mongoid/contextual/mongo_spec.rb +40 -2
  57. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  58. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  59. data/spec/mongoid/criteria/queryable/selectable_spec.rb +74 -6
  60. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  61. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  62. data/spec/mongoid/criteria_spec.rb +4 -1
  63. data/spec/mongoid/document_spec.rb +54 -0
  64. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  65. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  66. data/spec/mongoid/extensions/string_spec.rb +35 -7
  67. data/spec/mongoid/fields_spec.rb +1 -1
  68. data/spec/mongoid/findable_spec.rb +1 -1
  69. data/spec/mongoid/interceptable_spec.rb +1 -1
  70. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  71. data/spec/mongoid/matchable_spec.rb +26 -1
  72. data/spec/mongoid/persistable/deletable_spec.rb +19 -0
  73. data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
  74. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  75. data/spec/mongoid/persistable/settable_spec.rb +35 -1
  76. data/spec/mongoid/persistable_spec.rb +16 -16
  77. data/spec/mongoid/relations/embedded/many_spec.rb +246 -16
  78. data/spec/mongoid/scopable_spec.rb +13 -0
  79. data/spec/mongoid/threaded_spec.rb +68 -0
  80. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  81. data/spec/spec_helper.rb +79 -0
  82. data/spec/support/cluster_config.rb +158 -0
  83. data/spec/support/constraints.rb +101 -0
  84. data/spec/support/macros.rb +20 -0
  85. data/spec/support/spec_config.rb +42 -0
  86. metadata +471 -443
  87. metadata.gz.sig +0 -0
@@ -1130,6 +1130,19 @@ describe Mongoid::Scopable do
1130
1130
  it "sets the threading options" do
1131
1131
  Band.without_default_scope do
1132
1132
  expect(Mongoid::Threaded).to be_executing(:without_default_scope)
1133
+ expect(Mongoid::Threaded.without_default_scope?(Band)).to be(true)
1134
+ end
1135
+ end
1136
+
1137
+ it "suppresses default scope on the given model within the given block" do
1138
+ Appointment.without_default_scope do
1139
+ expect(Appointment.all.selector).to be_empty
1140
+ end
1141
+ end
1142
+
1143
+ it "does not affect other models' default scopes within the given block" do
1144
+ Appointment.without_default_scope do
1145
+ expect(Audio.all.selector).not_to be_empty
1133
1146
  end
1134
1147
  end
1135
1148
  end
@@ -233,4 +233,72 @@ describe Mongoid::Threaded do
233
233
  end
234
234
  end
235
235
  end
236
+
237
+ describe "#begin_without_default_scope" do
238
+
239
+ let(:klass) do
240
+ Appointment
241
+ end
242
+
243
+ after do
244
+ described_class.exit_without_default_scope(klass)
245
+ end
246
+
247
+ it "adds the given class to the without_default_scope stack" do
248
+ described_class.begin_without_default_scope(klass)
249
+
250
+ expect(described_class.stack(:without_default_scope)).to include(klass)
251
+ end
252
+ end
253
+
254
+ describe "#exit_without_default_scope" do
255
+
256
+ let(:klass) do
257
+ Appointment
258
+ end
259
+
260
+ before do
261
+ described_class.begin_without_default_scope(klass)
262
+ end
263
+
264
+ it "removes the given class from the without_default_scope stack" do
265
+ described_class.exit_without_default_scope(klass)
266
+
267
+ expect(described_class.stack(:without_default_scope)).not_to include(klass)
268
+ end
269
+ end
270
+
271
+ describe "#without_default_scope?" do
272
+
273
+ let(:klass) do
274
+ Appointment
275
+ end
276
+
277
+ context "when klass has begun without_default_scope" do
278
+
279
+ before do
280
+ described_class.begin_without_default_scope(klass)
281
+ end
282
+
283
+ after do
284
+ described_class.exit_without_default_scope(klass)
285
+ end
286
+
287
+ it "returns true" do
288
+ expect(described_class.without_default_scope?(klass)).to be(true)
289
+ end
290
+ end
291
+
292
+ context "when klass has exited without_default_scope" do
293
+
294
+ before do
295
+ described_class.begin_without_default_scope(klass)
296
+ described_class.exit_without_default_scope(klass)
297
+ end
298
+
299
+ it "returns false" do
300
+ expect(described_class.without_default_scope?(klass)).to be(false)
301
+ end
302
+ end
303
+ end
236
304
  end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+ require "mongoid/railties/controller_runtime"
3
+
4
+ describe "Mongoid::Railties::ControllerRuntime" do
5
+ controller_runtime = Mongoid::Railties::ControllerRuntime
6
+ collector = controller_runtime::Collector
7
+
8
+ def set_metric value
9
+ Thread.current["Mongoid.controller_runtime"] = value
10
+ end
11
+
12
+ def clear_metric!
13
+ set_metric 0
14
+ end
15
+
16
+ describe "Collector" do
17
+
18
+ it "stores the metric in thread-safe manner" do
19
+ clear_metric!
20
+ expect(collector.runtime).to eq(0)
21
+ set_metric 42
22
+ expect(collector.runtime).to eq(42)
23
+ end
24
+
25
+ it "sets metric on both succeeded and failed" do
26
+ instance = collector.new
27
+ event_payload = OpenStruct.new duration: 42
28
+
29
+ clear_metric!
30
+ instance.succeeded event_payload
31
+ expect(collector.runtime).to eq(42)
32
+
33
+ clear_metric!
34
+ instance.failed event_payload
35
+ expect(collector.runtime).to eq(42)
36
+ end
37
+
38
+ it "resets the metric and returns the value" do
39
+ clear_metric!
40
+ expect(collector.reset_runtime).to eq(0)
41
+ set_metric 42
42
+ expect(collector.reset_runtime).to eq(42)
43
+ expect(collector.runtime).to eq(0)
44
+ end
45
+
46
+ end
47
+
48
+ reference_controller_class = Class.new do
49
+ def process_action *_
50
+ @process_action = true
51
+ end
52
+
53
+ def cleanup_view_runtime *_
54
+ @cleanup_view_runtime.call
55
+ end
56
+
57
+ def append_info_to_payload *_
58
+ @append_info_to_payload = true
59
+ end
60
+
61
+ def self.log_process_action *_
62
+ @log_process_action.call
63
+ end
64
+ end
65
+
66
+ controller_class = Class.new reference_controller_class do
67
+ include controller_runtime::ControllerExtension
68
+ end
69
+
70
+ let(:controller){ controller_class.new }
71
+
72
+ it "resets the metric before each action" do
73
+ set_metric 42
74
+ controller.send(:process_action, 'foo')
75
+ expect(collector.runtime).to be(0)
76
+ expect(controller.instance_variable_get "@process_action").to be(true)
77
+ end
78
+
79
+ it "strips the metric of other sources of the runtime" do
80
+ set_metric 1
81
+ controller.instance_variable_set "@cleanup_view_runtime", ->{
82
+ controller.instance_variable_set "@cleanup_view_runtime", true
83
+ set_metric 13
84
+ 42
85
+ }
86
+ returned = controller.send :cleanup_view_runtime
87
+ expect(controller.instance_variable_get "@cleanup_view_runtime").to be(true)
88
+ expect(controller.mongoid_runtime).to eq(14)
89
+ expect(returned).to be(29)
90
+ end
91
+
92
+ it "appends the metric to payload" do
93
+ payload = {}
94
+ set_metric 42
95
+ controller.send :append_info_to_payload, payload
96
+ expect(controller.instance_variable_get "@append_info_to_payload").to be(true)
97
+ expect(payload[:mongoid_runtime]).to eq(42)
98
+ end
99
+
100
+ it "adds metric to log message" do
101
+ controller_class.instance_variable_set "@log_process_action", ->{
102
+ controller_class.instance_variable_set "@log_process_action", true
103
+ []
104
+ }
105
+ messages = controller_class.log_process_action mongoid_runtime: 42.101
106
+ expect(controller_class.instance_variable_get "@log_process_action").to be(true)
107
+ expect(messages).to eq(["MongoDB: 42.1ms"])
108
+ end
109
+
110
+ end
@@ -34,6 +34,10 @@ end
34
34
 
35
35
  require 'support/authorization'
36
36
  require 'support/expectations'
37
+ require 'support/macros'
38
+ require 'support/spec_config'
39
+ require 'support/cluster_config'
40
+ require 'support/constraints'
37
41
 
38
42
  # Give MongoDB time to start up on the travis ci environment.
39
43
  if (ENV['CI'] == 'travis' || ENV['CI'] == 'evergreen')
@@ -90,6 +94,7 @@ end
90
94
  def array_filters_supported?
91
95
  Mongoid::Clients.default.cluster.next_primary.features.array_filters_enabled?
92
96
  end
97
+ alias :sessions_supported? :array_filters_supported?
93
98
 
94
99
  # Set the database that the spec suite connects to.
95
100
  Mongoid.configure do |config|
@@ -126,8 +131,13 @@ end
126
131
  I18n.config.enforce_available_locales = false
127
132
 
128
133
  RSpec.configure do |config|
134
+ config.expect_with(:rspec) do |c|
135
+ c.syntax = [:should, :expect]
136
+ end
137
+
129
138
  config.raise_errors_for_deprecations!
130
139
  config.include(Mongoid::Expectations)
140
+ config.extend(Constraints)
131
141
 
132
142
  config.before(:suite) do
133
143
  client = Mongo::Client.new(["#{HOST}:#{PORT}"])
@@ -145,3 +155,72 @@ RSpec.configure do |config|
145
155
  Mongoid.purge!
146
156
  end
147
157
  end
158
+
159
+ # A subscriber to be used with the Ruby driver for testing.
160
+ #
161
+ # @since 6.4.0
162
+ class EventSubscriber
163
+
164
+ # The started events.
165
+ #
166
+ # @since 6.4.0
167
+ attr_reader :started_events
168
+
169
+ # The succeeded events.
170
+ #
171
+ # @since 6.4.0
172
+ attr_reader :succeeded_events
173
+
174
+ # The failed events.
175
+ #
176
+ # @since 6.4.0
177
+ attr_reader :failed_events
178
+
179
+ # Create the test event subscriber.
180
+ #
181
+ # @example Create the subscriber
182
+ # EventSubscriber.new
183
+ #
184
+ # @since 6.4.0
185
+ def initialize
186
+ @started_events = []
187
+ @succeeded_events = []
188
+ @failed_events = []
189
+ end
190
+
191
+ # Cache the succeeded event.
192
+ #
193
+ # @param [ Event ] event The event.
194
+ #
195
+ # @since 6.4.0
196
+ def succeeded(event)
197
+ @succeeded_events.push(event)
198
+ end
199
+
200
+ # Cache the started event.
201
+ #
202
+ # @param [ Event ] event The event.
203
+ #
204
+ # @since 6.4.0
205
+ def started(event)
206
+ @started_events.push(event)
207
+ end
208
+
209
+ # Cache the failed event.
210
+ #
211
+ # @param [ Event ] event The event.
212
+ #
213
+ # @since 6.4.0
214
+ def failed(event)
215
+ @failed_events.push(event)
216
+ end
217
+
218
+ # Clear all cached events.
219
+ #
220
+ # @since 6.4.0
221
+ def clear_events!
222
+ @started_events = []
223
+ @succeeded_events = []
224
+ @failed_events = []
225
+ end
226
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'singleton'
5
+
6
+ class ClusterConfig
7
+ include Singleton
8
+
9
+ def single_server?
10
+ determine_cluster_config
11
+ @single_server
12
+ end
13
+
14
+ def replica_set_name
15
+ determine_cluster_config
16
+ @replica_set_name
17
+ end
18
+
19
+ def server_version
20
+ determine_cluster_config
21
+ @server_version
22
+ end
23
+
24
+ def short_server_version
25
+ server_version.split('.')[0..1].join('.')
26
+ end
27
+
28
+ def fcv
29
+ determine_cluster_config
30
+ @fcv
31
+ end
32
+
33
+ # Per https://jira.mongodb.org/browse/SERVER-39052, working with FCV
34
+ # in sharded topologies is annoying. Also, FCV doesn't exist in servers
35
+ # less than 3.4. This method returns FCV on 3.4+ servers when in single
36
+ # or RS topologies, and otherwise returns the major.minor server version.
37
+ def fcv_ish
38
+ if server_version >= '3.4' && topology != :sharded
39
+ fcv
40
+ else
41
+ if short_server_version == '4.1'
42
+ '4.2'
43
+ else
44
+ short_server_version
45
+ end
46
+ end
47
+ end
48
+
49
+ def primary_address
50
+ determine_cluster_config
51
+ @primary_address
52
+ end
53
+
54
+ def primary_address_str
55
+ determine_cluster_config
56
+ @primary_address.seed
57
+ end
58
+
59
+ def primary_address_host
60
+ both = primary_address_str
61
+ both.split(':').first
62
+ end
63
+
64
+ def primary_address_port
65
+ both = primary_address_str
66
+ both.split(':')[1] || 27017
67
+ end
68
+
69
+ def primary_description
70
+ determine_cluster_config
71
+ @primary_description
72
+ end
73
+
74
+ # Try running a command on the admin database to see if the mongod was
75
+ # started with auth.
76
+ def auth_enabled?
77
+ if @auth_enabled.nil?
78
+ @auth_enabled = begin
79
+ basic_client.use(:admin).command(getCmdLineOpts: 1).first["argv"].include?("--auth")
80
+ rescue => e
81
+ e.message =~ /(not authorized)|(unauthorized)|(no users authenticated)|(requires authentication)/
82
+ end
83
+ end
84
+ @auth_enabled
85
+ end
86
+
87
+ def topology
88
+ determine_cluster_config
89
+ @topology
90
+ end
91
+
92
+ def storage_engine
93
+ @storage_engine ||= begin
94
+ # 2.6 does not have wired tiger
95
+ if short_server_version == '2.6'
96
+ :mmapv1
97
+ else
98
+ client = ClientRegistry.instance.global_client('root_authorized')
99
+ if topology == :sharded
100
+ shards = client.use(:admin).command(listShards: 1).first
101
+ shard = shards['shards'].first
102
+ address_str = shard['host'].sub(/^.*\//, '').sub(/,.*/, '')
103
+ client = ClusterTools.instance.direct_client(address_str,
104
+ SpecConfig.instance.test_options.merge(SpecConfig.instance.auth_options).merge(connect: :direct))
105
+ end
106
+ rv = client.use(:admin).command(serverStatus: 1).first
107
+ rv = rv['storageEngine']['name']
108
+ rv_map = {
109
+ 'wiredTiger' => :wired_tiger,
110
+ 'mmapv1' => :mmapv1,
111
+ }
112
+ rv_map[rv] || rv
113
+ end
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def determine_cluster_config
120
+ return if @primary_address
121
+
122
+ # Run all commands to figure out the cluster configuration from the same
123
+ # client. This is somewhat wasteful when running a single test, but reduces
124
+ # test runtime for the suite overall because all commands are sent on the
125
+ # same connection rather than each command connecting to the cluster by
126
+ # itself.
127
+ client = Mongoid::Clients.default
128
+
129
+ primary = client.cluster.next_primary
130
+ @primary_address = primary.address
131
+ @primary_description = primary.description
132
+ @replica_set_name = client.cluster.topology.replica_set_name
133
+
134
+ @topology ||= begin
135
+ topology = client.cluster.topology.class.name.sub(/.*::/, '')
136
+ topology = topology.gsub(/([A-Z])/) { |match| '_' + match.downcase }.sub(/^_/, '')
137
+ if topology =~ /^replica_set/
138
+ topology = 'replica_set'
139
+ end
140
+ topology.to_sym
141
+ end
142
+
143
+ @single_server = client.cluster.send(:servers_list).length == 1
144
+
145
+ @server_version = client.database.command(buildInfo: 1).first['version']
146
+
147
+ if @topology != :sharded && short_server_version >= '3.4'
148
+ rv = client.use(:admin).command(getParameter: 1, featureCompatibilityVersion: 1).first['featureCompatibilityVersion']
149
+ @fcv = rv['version'] || rv
150
+ end
151
+ end
152
+
153
+ def basic_client
154
+ # Do not cache the result here so that if the client gets closed,
155
+ # client registry reconnects it in subsequent tests
156
+ ClientRegistry.instance.global_client('basic')
157
+ end
158
+ end