mongoid 6.4.1 → 6.4.8

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 (58) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/Rakefile +26 -0
  5. data/lib/mongoid.rb +1 -1
  6. data/lib/mongoid/contextual/map_reduce.rb +1 -1
  7. data/lib/mongoid/criteria/modifiable.rb +12 -2
  8. data/lib/mongoid/criteria/queryable/selectable.rb +34 -7
  9. data/lib/mongoid/document.rb +4 -4
  10. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  11. data/lib/mongoid/extensions/regexp.rb +1 -0
  12. data/lib/mongoid/extensions/string.rb +3 -1
  13. data/lib/mongoid/matchable.rb +3 -0
  14. data/lib/mongoid/matchable/nor.rb +37 -0
  15. data/lib/mongoid/persistable/settable.rb +5 -5
  16. data/lib/mongoid/persistence_context.rb +4 -0
  17. data/lib/mongoid/query_cache.rb +64 -19
  18. data/lib/mongoid/railtie.rb +17 -0
  19. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  20. data/lib/mongoid/scopable.rb +3 -3
  21. data/lib/mongoid/threaded.rb +36 -0
  22. data/lib/mongoid/version.rb +1 -1
  23. data/spec/app/models/array_field.rb +7 -0
  24. data/spec/app/models/delegating_patient.rb +16 -0
  25. data/spec/integration/document_spec.rb +22 -0
  26. data/spec/mongoid/clients/factory_spec.rb +52 -28
  27. data/spec/mongoid/clients/options_spec.rb +30 -15
  28. data/spec/mongoid/clients/sessions_spec.rb +12 -3
  29. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  30. data/spec/mongoid/contextual/mongo_spec.rb +2 -2
  31. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  32. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  33. data/spec/mongoid/criteria/queryable/selectable_spec.rb +42 -3
  34. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  35. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  36. data/spec/mongoid/criteria_spec.rb +4 -1
  37. data/spec/mongoid/document_spec.rb +54 -0
  38. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  39. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  40. data/spec/mongoid/extensions/string_spec.rb +35 -7
  41. data/spec/mongoid/fields_spec.rb +1 -1
  42. data/spec/mongoid/findable_spec.rb +1 -1
  43. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  44. data/spec/mongoid/matchable_spec.rb +26 -1
  45. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  46. data/spec/mongoid/persistable/settable_spec.rb +19 -0
  47. data/spec/mongoid/query_cache_spec.rb +87 -18
  48. data/spec/mongoid/scopable_spec.rb +13 -0
  49. data/spec/mongoid/threaded_spec.rb +68 -0
  50. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  51. data/spec/spec_helper.rb +10 -0
  52. data/spec/support/cluster_config.rb +158 -0
  53. data/spec/support/constraints.rb +101 -0
  54. data/spec/support/macros.rb +20 -0
  55. data/spec/support/session_registry.rb +50 -0
  56. data/spec/support/spec_config.rb +42 -0
  57. metadata +43 -23
  58. metadata.gz.sig +0 -0
@@ -102,6 +102,23 @@ module Rails
102
102
  puts "There is a configuration error with the current mongoid.yml."
103
103
  puts e.message
104
104
  end
105
+
106
+ # Include Controller extension that measures Mongoid runtime
107
+ # during request processing. The value then appears in Rails'
108
+ # instrumentation event `process_action.action_controller`.
109
+ #
110
+ # The measurement is made via internal Mongo monitoring subscription
111
+ initializer "mongoid.runtime-metric" do
112
+ require "mongoid/railties/controller_runtime"
113
+
114
+ ActiveSupport.on_load :action_controller do
115
+ include ::Mongoid::Railties::ControllerRuntime::ControllerExtension
116
+ end
117
+
118
+ Mongo::Monitoring::Global.subscribe Mongo::Monitoring::COMMAND,
119
+ ::Mongoid::Railties::ControllerRuntime::Collector.new
120
+ end
121
+
105
122
  end
106
123
  end
107
124
  end
@@ -0,0 +1,86 @@
1
+ module Mongoid
2
+ module Railties
3
+ module ControllerRuntime
4
+
5
+ # This extension mimics the Rails' internal method to
6
+ # measure ActiveRecord runtime during request processing.
7
+ # It appends MongoDB runtime value (`mongoid_runtime`) into payload
8
+ # of instrumentation event `process_action.action_controller`.
9
+ module ControllerExtension
10
+ extend ActiveSupport::Concern
11
+
12
+ protected
13
+
14
+ attr_internal :mongoid_runtime
15
+
16
+ # Reset the runtime before each action.
17
+ def process_action(action, *args)
18
+ Collector.reset_runtime
19
+ super
20
+ end
21
+
22
+ # Override to collect the measurements.
23
+ def cleanup_view_runtime
24
+ mongo_rt_before_render = Collector.reset_runtime
25
+ runtime = super
26
+ mongo_rt_after_render = Collector.reset_runtime
27
+ self.mongoid_runtime = mongo_rt_before_render + mongo_rt_after_render
28
+ runtime - mongo_rt_after_render
29
+ end
30
+
31
+ # Add the measurement to the instrumentation event payload.
32
+ def append_info_to_payload(payload)
33
+ super
34
+ payload[:mongoid_runtime] = (mongoid_runtime || 0) + Collector.reset_runtime
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ # Append MongoDB runtime information to ActionController runtime
40
+ # log message.
41
+ def log_process_action(payload)
42
+ messages = super
43
+ mongoid_runtime = payload[:mongoid_runtime]
44
+ messages << ("MongoDB: %.1fms" % mongoid_runtime.to_f) if mongoid_runtime
45
+ messages
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ # The Collector of MongoDB runtime metric, that subscribes to Mongo
53
+ # driver command monitoring. Stores the value within a thread-local
54
+ # variable to provide correct accounting when an application issues
55
+ # MongoDB operations from background threads.
56
+ class Collector
57
+
58
+ VARIABLE_NAME = "Mongoid.controller_runtime".freeze
59
+
60
+ def started _; end
61
+
62
+ def _completed e
63
+ Collector.runtime += e.duration
64
+ end
65
+ alias :succeeded :_completed
66
+ alias :failed :_completed
67
+
68
+ def self.runtime
69
+ Thread.current[VARIABLE_NAME] ||= 0
70
+ end
71
+
72
+ def self.runtime= value
73
+ Thread.current[VARIABLE_NAME] = value
74
+ end
75
+
76
+ def self.reset_runtime
77
+ to_now = runtime
78
+ self.runtime = 0
79
+ to_now
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -102,7 +102,7 @@ module Mongoid
102
102
  #
103
103
  # @since 3.0.0
104
104
  def default_scopable?
105
- default_scoping? && !Threaded.executing?(:without_default_scope)
105
+ default_scoping? && !Threaded.without_default_scope?(self)
106
106
  end
107
107
 
108
108
  # Get a queryable, either the last one on the scope stack or a fresh one.
@@ -244,10 +244,10 @@ module Mongoid
244
244
  #
245
245
  # @since 3.0.0
246
246
  def without_default_scope
247
- Threaded.begin_execution("without_default_scope")
247
+ Threaded.begin_without_default_scope(self)
248
248
  yield
249
249
  ensure
250
- Threaded.exit_execution("without_default_scope")
250
+ Threaded.exit_without_default_scope(self)
251
251
  end
252
252
 
253
253
  private
@@ -163,6 +163,30 @@ module Mongoid
163
163
  validations_for(document.class).delete_one(document._id)
164
164
  end
165
165
 
166
+ # Begin suppressing default scopes for given model on the current thread.
167
+ #
168
+ # @example Begin without default scope stack.
169
+ # Threaded.begin_without_default_scope(klass)
170
+ #
171
+ # @param [ Class ] klass The model to suppress default scoping on.
172
+ #
173
+ # @api private
174
+ def begin_without_default_scope(klass)
175
+ stack(:without_default_scope).push(klass)
176
+ end
177
+
178
+ # Exit suppressing default scopes for given model on the current thread.
179
+ #
180
+ # @example Exit without default scope stack.
181
+ # Threaded.exit_without_default_scope(klass)
182
+ #
183
+ # @param [ Class ] klass The model to unsuppress default scoping on.
184
+ #
185
+ # @api private
186
+ def exit_without_default_scope(klass)
187
+ stack(:without_default_scope).delete(klass)
188
+ end
189
+
166
190
  # Get the global client override.
167
191
  #
168
192
  # @example Get the global client override.
@@ -247,6 +271,18 @@ module Mongoid
247
271
  end
248
272
  end
249
273
 
274
+ # Is the given klass' default scope suppressed on the current thread?
275
+ #
276
+ # @example Is the given klass' default scope suppressed?
277
+ # Threaded.without_default_scope?(klass)
278
+ #
279
+ # @param [ Class ] klass The model to check for default scope suppression.
280
+ #
281
+ # @api private
282
+ def without_default_scope?(klass)
283
+ stack(:without_default_scope).include?(klass)
284
+ end
285
+
250
286
  # Is the document autosaved on the current thread?
251
287
  #
252
288
  # @example Is the document autosaved?
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "6.4.1"
3
+ VERSION = "6.4.8"
4
4
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ArrayField
4
+ include Mongoid::Document
5
+
6
+ field :af, type: Array
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ class DelegatingPatient
5
+ include Mongoid::Document
6
+
7
+ embeds_one :email
8
+
9
+ # Instance level delegation
10
+ delegate :address, to: :email
11
+
12
+ class << self
13
+ # Class level delegation
14
+ delegate :default_client, to: ::Mongoid
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe Mongoid::Document do
7
+ context 'when including class uses delegate' do
8
+ let(:patient) do
9
+ DelegatingPatient.new(
10
+ email: Email.new(address: 'test@example.com'),
11
+ )
12
+ end
13
+
14
+ it 'works for instance level delegation' do
15
+ patient.address.should == 'test@example.com'
16
+ end
17
+
18
+ it 'works for class level delegation' do
19
+ DelegatingPatient.default_client.should be Mongoid.default_client
20
+ end
21
+ end
22
+ end
@@ -1,7 +1,35 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  require "spec_helper"
2
5
 
3
6
  describe Mongoid::Clients::Factory do
4
7
 
8
+ shared_examples_for 'includes seed address' do
9
+ let(:configured_address) do
10
+ address = SpecConfig.instance.addresses.first
11
+ unless address.include?(':')
12
+ address = "#{address}:27017"
13
+ end
14
+ address
15
+ end
16
+
17
+ let(:expected_addresses) do
18
+ [
19
+ configured_address,
20
+ configured_address.sub(/\Alocalhost:/, '127.0.0.1:'),
21
+ configured_address.sub(/\A127\.0\.0\.1:/, 'localhost:'),
22
+ ].uniq
23
+ end
24
+
25
+ it 'includes seed address' do
26
+ ok = cluster_addresses.any? do |address|
27
+ expected_addresses.include?(address)
28
+ end
29
+ expect(ok).to be true
30
+ end
31
+ end
32
+
5
33
  describe ".create" do
6
34
 
7
35
  context "when provided a name" do
@@ -12,8 +40,8 @@ describe Mongoid::Clients::Factory do
12
40
 
13
41
  let(:config) do
14
42
  {
15
- default: { hosts: [ "127.0.0.1:27017" ], database: database_id },
16
- secondary: { hosts: [ "127.0.0.1:27017" ], database: database_id }
43
+ default: { hosts: SpecConfig.instance.addresses, database: database_id },
44
+ secondary: { hosts: SpecConfig.instance.addresses, database: database_id }
17
45
  }
18
46
  end
19
47
 
@@ -37,10 +65,12 @@ describe Mongoid::Clients::Factory do
37
65
  expect(client).to be_a(Mongo::Client)
38
66
  end
39
67
 
40
- it "sets the cluster's seeds" do
41
- expect(cluster.addresses.first.to_s).to eq("127.0.0.1:27017")
68
+ let(:cluster_addresses) do
69
+ cluster.addresses.map(&:to_s)
42
70
  end
43
71
 
72
+ it_behaves_like 'includes seed address'
73
+
44
74
  it "sets the platform to Mongoid's platform constant" do
45
75
  expect(client.options[:platform]).to eq(Mongoid::PLATFORM_DETAILS)
46
76
  end
@@ -80,11 +110,11 @@ describe Mongoid::Clients::Factory do
80
110
  end
81
111
 
82
112
  it "sets the cluster's seed ports to 27017" do
83
- expect(cluster.addresses.first.to_s).to eq("127.0.0.1:27017")
113
+ expect(%w(127.0.0.1:27017 localhost:27017)).to include(cluster.addresses.first.to_s)
84
114
  end
85
115
 
86
116
  it "sets ips with no ports to 27017" do
87
- expect(default.cluster.addresses.first.to_s).to eq("127.0.0.1:27017")
117
+ expect(%w(127.0.0.1:27017 localhost:27017)).to include(cluster.addresses.first.to_s)
88
118
  end
89
119
  end
90
120
 
@@ -120,7 +150,7 @@ describe Mongoid::Clients::Factory do
120
150
  end
121
151
 
122
152
  it "sets the cluster's seeds" do
123
- expect(cluster.addresses.first.to_s).to eq("127.0.0.1:27017")
153
+ expect(%w(127.0.0.1:27017 localhost:27017)).to include(cluster.addresses.first.to_s)
124
154
  end
125
155
 
126
156
  it "sets the database" do
@@ -132,8 +162,8 @@ describe Mongoid::Clients::Factory do
132
162
 
133
163
  let(:config) do
134
164
  {
135
- default: { hosts: [ "127.0.0.1:27017" ], database: database_id },
136
- secondary: { uri: "mongodb://127.0.0.1:27017,127.0.0.1:27018/mongoid_test" }
165
+ default: { hosts: [ "127.0.0.1:1234" ], database: database_id, server_selection_timeout: 1 },
166
+ secondary: { uri: "mongodb://127.0.0.1:1234,127.0.0.1:5678/mongoid_test?serverSelectionTimeoutMS=1000" }
137
167
  }
138
168
  end
139
169
 
@@ -162,7 +192,7 @@ describe Mongoid::Clients::Factory do
162
192
  end
163
193
 
164
194
  it "sets the cluster's seeds" do
165
- expect(seeds).to eq([ "127.0.0.1:27017", "127.0.0.1:27018" ])
195
+ expect(seeds).to eq([ "127.0.0.1:1234", "127.0.0.1:5678" ])
166
196
  end
167
197
  end
168
198
  end
@@ -181,7 +211,7 @@ describe Mongoid::Clients::Factory do
181
211
  context "when no name is provided" do
182
212
 
183
213
  let(:config) do
184
- { default: { hosts: ["127.0.0.1:27017"], database: database_id }}
214
+ { default: { hosts: SpecConfig.instance.addresses, database: database_id }}
185
215
  end
186
216
 
187
217
  before do
@@ -200,17 +230,15 @@ describe Mongoid::Clients::Factory do
200
230
  client.cluster
201
231
  end
202
232
 
203
- let(:seeds) do
204
- cluster.addresses.map{ |address| address.to_s }
233
+ let(:cluster_addresses) do
234
+ cluster.addresses.map(&:to_s)
205
235
  end
206
236
 
207
237
  it "returns the default client" do
208
238
  expect(client).to be_a(Mongo::Client)
209
239
  end
210
240
 
211
- it "sets the cluster's seeds" do
212
- expect(seeds).to eq([ "127.0.0.1:27017" ])
213
- end
241
+ it_behaves_like 'includes seed address'
214
242
  end
215
243
 
216
244
  context "when nil is provided and no default config" do
@@ -230,7 +258,7 @@ describe Mongoid::Clients::Factory do
230
258
  describe ".default" do
231
259
 
232
260
  let(:config) do
233
- { default: { hosts: ["127.0.0.1:27017"], database: database_id }}
261
+ { default: { hosts: SpecConfig.instance.addresses, database: database_id }}
234
262
  end
235
263
 
236
264
  before do
@@ -249,17 +277,15 @@ describe Mongoid::Clients::Factory do
249
277
  client.cluster
250
278
  end
251
279
 
252
- let(:seeds) do
253
- cluster.addresses.map{ |address| address.to_s }
280
+ let(:cluster_addresses) do
281
+ cluster.addresses.map(&:to_s)
254
282
  end
255
283
 
256
284
  it "returns the default client" do
257
285
  expect(client).to be_a(Mongo::Client)
258
286
  end
259
287
 
260
- it "sets the cluster's seeds" do
261
- expect(seeds).to eq([ "127.0.0.1:27017" ])
262
- end
288
+ it_behaves_like 'includes seed address'
263
289
  end
264
290
 
265
291
  context "when options are provided with string keys" do
@@ -267,7 +293,7 @@ describe Mongoid::Clients::Factory do
267
293
  let(:config) do
268
294
  {
269
295
  default: {
270
- hosts: [ "127.0.0.1:27017" ],
296
+ hosts: SpecConfig.instance.addresses,
271
297
  database: database_id,
272
298
  options: {
273
299
  "server_selection_timeout" => 10,
@@ -293,17 +319,15 @@ describe Mongoid::Clients::Factory do
293
319
  client.cluster
294
320
  end
295
321
 
296
- let(:seeds) do
297
- cluster.addresses.map{ |address| address.to_s }
322
+ let(:cluster_addresses) do
323
+ cluster.addresses.map(&:to_s)
298
324
  end
299
325
 
300
326
  it "returns the default client" do
301
327
  expect(client).to be_a(Mongo::Client)
302
328
  end
303
329
 
304
- it "sets the cluster's seeds" do
305
- expect(seeds).to eq([ "127.0.0.1:27017" ])
306
- end
330
+ it_behaves_like 'includes seed address'
307
331
 
308
332
  it "sets the server selection timeout" do
309
333
  expect(cluster.options[:server_selection_timeout]).to eq(10)
@@ -177,25 +177,40 @@ describe Mongoid::Clients::Options do
177
177
 
178
178
  context 'when returning a criteria' do
179
179
 
180
- let(:context_and_criteria) do
181
- collection = nil
182
- cxt = Band.with(read: :secondary) do |klass|
183
- collection = klass.all.collection
184
- klass.persistence_context
180
+ shared_context 'applies secondary read preference' do
181
+
182
+ let(:context_and_criteria) do
183
+ collection = nil
184
+ cxt = Band.with(read_secondary_option) do |klass|
185
+ collection = klass.all.collection
186
+ klass.persistence_context
187
+ end
188
+ [ cxt, collection ]
185
189
  end
186
- [ cxt, collection ]
187
- end
188
190
 
189
- let(:persistence_context) do
190
- context_and_criteria[0]
191
+ let(:persistence_context) do
192
+ context_and_criteria[0]
193
+ end
194
+
195
+ let(:client) do
196
+ context_and_criteria[1].client
197
+ end
198
+
199
+ it 'applies the options to the criteria client' do
200
+ expect(client.options['read']).to eq('mode' => :secondary)
201
+ end
191
202
  end
192
203
 
193
- let(:client) do
194
- context_and_criteria[1].client
204
+ context 'read: :secondary shorthand' do
205
+ let(:read_secondary_option) { {read: :secondary} }
206
+
207
+ it_behaves_like 'applies secondary read preference'
195
208
  end
196
209
 
197
- it 'applies the options to the criteria client' do
198
- expect(client.options['read']).to eq(:secondary)
210
+ context 'read: {mode: :secondary}' do
211
+ let(:read_secondary_option) { {read: {mode: :secondary}} }
212
+
213
+ it_behaves_like 'applies secondary read preference'
199
214
  end
200
215
  end
201
216
 
@@ -339,7 +354,7 @@ describe Mongoid::Clients::Options do
339
354
  band.mongo_client.database.command(serverStatus: 1).first['connections']['current']
340
355
  end
341
356
 
342
- let!(:connections_and_cluster_during) do
357
+ let(:connections_and_cluster_during) do
343
358
  connections = nil
344
359
  cluster = band.with(options) do |b|
345
360
  b.reload
@@ -381,7 +396,7 @@ describe Mongoid::Clients::Options do
381
396
  end
382
397
 
383
398
  it 'disconnects the new cluster when the block exits' do
384
- expect(connections_before).to eq(connections_after)
399
+ expect(connections_after).to eq(connections_before)
385
400
  end
386
401
  end
387
402