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
@@ -213,6 +213,10 @@ module Mongoid
213
213
 
214
214
  private
215
215
 
216
+ def _session
217
+ base.send(:_session)
218
+ end
219
+
216
220
  # Find the first object given the supplied attributes or create/initialize it.
217
221
  #
218
222
  # @example Find or create|initialize.
@@ -477,7 +477,7 @@ module Mongoid
477
477
  # @since 3.0.0
478
478
  def persist_delayed(docs, inserts)
479
479
  unless docs.empty?
480
- collection.insert_many(inserts)
480
+ collection.insert_many(inserts, session: _session)
481
481
  docs.each do |doc|
482
482
  doc.new_record = false
483
483
  doc.run_after_callbacks(:create, :save)
@@ -31,7 +31,7 @@ module Mongoid
31
31
  touches = touch_atomic_updates(field)
32
32
  unless touches["$set"].blank?
33
33
  selector = atomic_selector
34
- _root.collection.find(selector).update_one(positionally(selector, touches))
34
+ _root.collection.find(selector).update_one(positionally(selector, touches), session: _session)
35
35
  end
36
36
  run_callbacks(:touch)
37
37
  true
@@ -58,7 +58,7 @@ module Mongoid
58
58
  #
59
59
  # @since 2.3.2
60
60
  def reload_root_document
61
- {}.merge(collection.find(_id: _id).read(mode: :primary).first || {})
61
+ {}.merge(collection.find({ _id: _id }, session: _session).read(mode: :primary).first || {})
62
62
  end
63
63
 
64
64
  # Reload the embedded document.
@@ -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
@@ -44,7 +44,7 @@ module Mongoid
44
44
  models.each do |model|
45
45
  unless model.embedded?
46
46
  begin
47
- model.collection.indexes.each do |index|
47
+ model.collection.indexes(session: model.send(:_session)).each do |index|
48
48
  # ignore default index
49
49
  unless index['name'] == '_id_'
50
50
  key = index['key'].symbolize_keys
@@ -77,7 +77,7 @@ module Mongoid
77
77
  indexes.each do |index|
78
78
  key = index['key'].symbolize_keys
79
79
  collection = model.collection
80
- collection.indexes.drop_one(key)
80
+ collection.indexes(session: model.send(:_session)).drop_one(key)
81
81
  logger.info(
82
82
  "MONGOID: Removed index '#{index['name']}' on collection " +
83
83
  "'#{collection.name}' in database '#{collection.database.name}'."
@@ -107,6 +107,7 @@ module Mongoid
107
107
  end
108
108
 
109
109
  private
110
+
110
111
  def logger
111
112
  Mongoid.logger
112
113
  end
@@ -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?
@@ -325,5 +361,43 @@ module Mongoid
325
361
  def validations_for(klass)
326
362
  validations[klass] ||= []
327
363
  end
364
+
365
+ # Cache a session for this thread.
366
+ #
367
+ # @example Save a session for this thread.
368
+ # Threaded.set_session(session)
369
+ #
370
+ # @param [ Mongo::Session ] session The session to save.
371
+ #
372
+ # @since 6.4.0
373
+ def set_session(session)
374
+ Thread.current[:session] = session
375
+ end
376
+
377
+ # Get the cached session for this thread.
378
+ #
379
+ # @example Get the session for this thread.
380
+ # Threaded.get_session
381
+ #
382
+ # @return [ Mongo::Session, nil ] The session cached on this thread or nil.
383
+ #
384
+ # @since 6.4.0
385
+ def get_session
386
+ Thread.current[:session]
387
+ end
388
+
389
+ # Clear the cached session for this thread.
390
+ #
391
+ # @example Clear this thread's session.
392
+ # Threaded.clear_session
393
+ #
394
+ # @return [ nil ]
395
+ #
396
+ # @since 6.4.0
397
+ def clear_session
398
+ session = get_session
399
+ session.end_session if session
400
+ Thread.current[:session] = nil
401
+ end
328
402
  end
329
403
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Mongoid
3
- VERSION = "6.3.0"
3
+ VERSION = "6.4.5"
4
4
  end
@@ -11,6 +11,10 @@ development:
11
11
  hosts:
12
12
  - localhost:27017
13
13
  options:
14
+ # Note that all options listed below are Ruby driver client options (the mongo gem).
15
+ # Please refer to the driver documentation of the version of the mongo gem you are using
16
+ # for the most up-to-date list of options.
17
+ #
14
18
  # Change the default write concern. (default = { w: 1 })
15
19
  # write:
16
20
  # w: 1
@@ -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
@@ -205,6 +205,10 @@ describe Mongoid::Attributes::Nested do
205
205
  Post.accepts_nested_attributes_for :person
206
206
  end
207
207
 
208
+ after do
209
+ Post.reset_callbacks(:save)
210
+ end
211
+
208
212
  let(:post) do
209
213
  Post.new(person_attributes: { title: "Sir" })
210
214
  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)