mongoid 6.3.0 → 6.4.5

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 (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
@@ -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
 
@@ -0,0 +1,334 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Clients::Sessions do
4
+
5
+ before(:all) do
6
+ CONFIG[:clients][:other] = CONFIG[:clients][:default].dup
7
+ CONFIG[:clients][:other][:database] = 'other'
8
+ Mongoid::Clients.clients.values.each(&:close)
9
+ Mongoid::Config.send(:clients=, CONFIG[:clients])
10
+ Mongoid::Clients.with_name(:other).subscribe(Mongo::Monitoring::COMMAND, EventSubscriber.new)
11
+ end
12
+
13
+ after(:all) do
14
+ Mongoid::Clients.with_name(:other).close
15
+ Mongoid::Clients.clients.delete(:other)
16
+ end
17
+
18
+ let(:subscriber) do
19
+ client = Mongoid::Clients.with_name(:other)
20
+ monitoring = if client.respond_to?(:monitoring, true)
21
+ client.send(:monitoring)
22
+ else
23
+ # driver 2.5
24
+ client.instance_variable_get('@monitoring')
25
+ end
26
+ monitoring.subscribers['Command'].find do |s|
27
+ s.is_a?(EventSubscriber)
28
+ end
29
+ end
30
+
31
+ let(:insert_events) do
32
+ # Driver 2.5 sends command_name as a symbol
33
+ subscriber.started_events.select { |event| event.command_name.to_s == 'insert' }
34
+ end
35
+
36
+ let(:update_events) do
37
+ # Driver 2.5 sends command_name as a symbol
38
+ subscriber.started_events.select { |event| event.command_name.to_s == 'update' }
39
+ end
40
+
41
+ context 'when a session is used on a model class' do
42
+
43
+ context 'when sessions are supported', if: sessions_supported? do
44
+
45
+ around do |example|
46
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
47
+ subscriber.clear_events!
48
+ Person.with(client: :other) do
49
+ example.run
50
+ end
51
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
52
+ end
53
+
54
+ context 'when another thread is started' do
55
+
56
+ let!(:last_use_diff) do
57
+ Person.with_session do |session|
58
+ Person.create
59
+ Person.create
60
+ last_use = session.instance_variable_get(:@server_session).last_use
61
+ Thread.new { Person.create }.value
62
+ session.instance_variable_get(:@server_session).last_use - last_use
63
+ end
64
+ end
65
+
66
+ it 'does not use the session for that thread' do
67
+ expect(Person.count).to be(2)
68
+ expect(Person.with(client: :default) { Person.count }).to be(1)
69
+ lsids_sent = insert_events.collect { |event| event.command['lsid'] }
70
+ expect(lsids_sent.size).to eq(2)
71
+ expect(lsids_sent.uniq.size).to eq(1)
72
+ expect(last_use_diff).to eq(0)
73
+ end
74
+ end
75
+
76
+ context 'when the operations in the session block are all on the class' do
77
+
78
+ before do
79
+ Person.with_session do
80
+ Person.create
81
+ Person.create
82
+ end
83
+ end
84
+
85
+ it 'uses a single session id for all operations on the class' do
86
+ expect(Person.count).to be(2)
87
+ lsids_sent = insert_events.collect { |event| event.command['lsid'] }
88
+ expect(lsids_sent.size).to eq(2)
89
+ expect(lsids_sent.uniq.size).to eq(1)
90
+ end
91
+ end
92
+
93
+ context 'when the operations in the session block are also on another class' do
94
+
95
+ context 'when the other class uses the same client' do
96
+
97
+ before do
98
+ Post.with(client: :other) do
99
+ Person.with_session do
100
+ Person.create
101
+ Person.create
102
+ Post.create
103
+ end
104
+ end
105
+ end
106
+
107
+ it 'uses a single session id for all operations on the class' do
108
+ expect(Post.with(client: :other) { |klass| klass.count }).to be(1)
109
+ lsids_sent = insert_events.collect { |event| event.command['lsid'] }
110
+ expect(lsids_sent.size).to eq(3)
111
+ expect(lsids_sent.uniq.size).to eq(1)
112
+ end
113
+ end
114
+
115
+ context 'when the other class uses a different client' do
116
+
117
+ let!(:error) do
118
+ e = nil
119
+ begin
120
+ Person.with_session do
121
+ Person.create
122
+ Person.create
123
+ Post.create
124
+ end
125
+ rescue => ex
126
+ e = ex
127
+ end
128
+ e
129
+ end
130
+
131
+ it 'raises an error' do
132
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
133
+ end
134
+
135
+ it 'uses a single session id for all operations on the class' do
136
+ expect(Person.count).to be(2)
137
+ lsids_sent = insert_events.collect { |event| event.command['lsid'] }
138
+ expect(lsids_sent.size).to eq(2)
139
+ expect(lsids_sent.uniq.size).to eq(1)
140
+ end
141
+ end
142
+
143
+ context 'when sessions are nested' do
144
+
145
+ let!(:error) do
146
+ e = nil
147
+ begin
148
+ Person.with_session do
149
+ Person.with_session do
150
+ Person.create
151
+ Post.create
152
+ end
153
+ end
154
+ rescue => ex
155
+ e = ex
156
+ end
157
+ e
158
+ end
159
+
160
+ it 'raises an error' do
161
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
162
+ end
163
+
164
+ it 'does not execute any operations' do
165
+ expect(Person.count).to be(0)
166
+ expect(insert_events).to be_empty
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ context 'when sessions are not supported', unless: sessions_supported? do
173
+
174
+ let!(:error) do
175
+ e = nil
176
+ begin
177
+ Person.with_session {}
178
+ rescue => ex
179
+ e = ex
180
+ end
181
+ e
182
+ end
183
+
184
+ it 'raises a sessions not supported error' do
185
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
186
+ expect(error.message).to include('not supported')
187
+ end
188
+ end
189
+ end
190
+
191
+ context 'when a session is used on a model instance' do
192
+
193
+ let!(:person) do
194
+ Person.with(client: :other) do |klass|
195
+ klass.create
196
+ end
197
+ end
198
+
199
+ context 'when sessions are supported', if: sessions_supported? do
200
+
201
+ around do |example|
202
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
203
+ subscriber.clear_events!
204
+ person.with(client: :other) do
205
+ example.run
206
+ end
207
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
208
+ end
209
+
210
+ context 'when the operations in the session block are all on the instance' do
211
+
212
+ before do
213
+ person.with_session do
214
+ person.username = 'Emily'
215
+ person.save
216
+ person.age = 80
217
+ person.save
218
+ end
219
+ end
220
+
221
+ it 'uses a single session id for all operations on the class' do
222
+ expect(person.reload.username).to eq('Emily')
223
+ expect(person.reload.age).to eq(80)
224
+ lsids_sent = update_events.collect { |event| event.command['lsid'] }
225
+ expect(lsids_sent.size).to eq(2)
226
+ expect(lsids_sent.uniq.size).to eq(1)
227
+ end
228
+ end
229
+
230
+ context 'when the operations in the session block are also on another class' do
231
+
232
+ context 'when the other class uses the same client' do
233
+
234
+ before do
235
+ Post.with(client: :other) do
236
+ person.with_session do
237
+ person.username = 'Emily'
238
+ person.save
239
+ person.posts << Post.create
240
+ end
241
+ end
242
+ end
243
+
244
+ it 'uses a single session id for all operations on the class' do
245
+ expect(person.reload.username).to eq('Emily')
246
+ expect(Post.with(client: :other) { Post.count }).to be(1)
247
+ update_lsids_sent = update_events.collect { |event| event.command['lsid'] }
248
+ expect(update_lsids_sent.size).to eq(3) # person update, counter cache, post assignment
249
+ expect(update_lsids_sent.uniq.size).to eq(1) # person update, counter cache, post assignment
250
+ insert_lsids_sent = insert_events.collect { |event| event.command['lsid'] }
251
+ expect(insert_lsids_sent.size).to eq(2)
252
+ expect(insert_lsids_sent.uniq.size).to eq(1)
253
+ expect(update_lsids_sent.uniq).to eq(insert_lsids_sent.uniq)
254
+ end
255
+ end
256
+
257
+ context 'when the other class uses a different client' do
258
+
259
+ let!(:error) do
260
+ e = nil
261
+ begin
262
+ person.with_session do
263
+ person.username = 'Emily'
264
+ person.save
265
+ person.posts << Post.create
266
+ end
267
+ rescue => ex
268
+ e = ex
269
+ end
270
+ e
271
+ end
272
+
273
+ it 'raises an error' do
274
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
275
+ end
276
+
277
+ it 'uses a single session id for all operations on the class' do
278
+ expect(person.reload.username).to eq('Emily')
279
+ expect(Post.count).to be(0)
280
+ update_lsids_sent = update_events.collect { |event| event.command['lsid'] }
281
+ expect(update_lsids_sent.size).to eq(1)
282
+ end
283
+ end
284
+
285
+ context 'when sessions are nested' do
286
+
287
+ let!(:error) do
288
+ e = nil
289
+ begin
290
+ person.with_session do
291
+ person.with_session do
292
+ person.username = 'Emily'
293
+ person.save
294
+ person.posts << Post.create
295
+ end
296
+ end
297
+ rescue => ex
298
+ e = ex
299
+ end
300
+ e
301
+ end
302
+
303
+ it 'raises an error' do
304
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
305
+ end
306
+
307
+ it 'does not execute any operations' do
308
+ expect(person.reload.username).not_to eq('Emily')
309
+ expect(Post.count).to be(0)
310
+ expect(update_events).to be_empty
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ context 'when sessions are not supported', unless: sessions_supported? do
317
+
318
+ let!(:error) do
319
+ e = nil
320
+ begin
321
+ person.with_session {}
322
+ rescue => ex
323
+ e = ex
324
+ end
325
+ e
326
+ end
327
+
328
+ it 'raises a sessions not supported error' do
329
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
330
+ expect(error.message).to include('not supported')
331
+ end
332
+ end
333
+ end
334
+ end
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Mongoid::Contextual::GeoNear do
4
+ max_server_version '4.0'
4
5
 
5
6
  describe "#average_distance" do
6
7
 
@@ -275,6 +275,23 @@ describe Mongoid::Contextual::Mongo do
275
275
  expect(Band.count).to eq(0)
276
276
  end
277
277
  end
278
+
279
+ context 'when the write concern is unacknowledged' do
280
+
281
+ let(:criteria) do
282
+ Band.all
283
+ end
284
+
285
+ let!(:deleted) do
286
+ criteria.with(write: { w: 0 }) do |crit|
287
+ crit.send(method)
288
+ end
289
+ end
290
+
291
+ it 'returns 0' do
292
+ expect(deleted).to eq(0)
293
+ end
294
+ end
278
295
  end
279
296
  end
280
297
 
@@ -363,6 +380,27 @@ describe Mongoid::Contextual::Mongo do
363
380
  end
364
381
  end
365
382
  end
383
+
384
+ context 'when the write concern is unacknowledged' do
385
+
386
+ before do
387
+ 2.times { Band.create }
388
+ end
389
+
390
+ let(:criteria) do
391
+ Band.all
392
+ end
393
+
394
+ let!(:deleted) do
395
+ criteria.with(write: { w: 0 }) do |crit|
396
+ crit.send(method)
397
+ end
398
+ end
399
+
400
+ it 'returns 0' do
401
+ expect(deleted).to eq(0)
402
+ end
403
+ end
366
404
  end
367
405
 
368
406
  describe "#distinct" do
@@ -413,7 +451,7 @@ describe Mongoid::Contextual::Mongo do
413
451
  end
414
452
 
415
453
  it "returns the distinct field values" do
416
- expect(context.distinct(:years)).to eq([ 30, 25 ])
454
+ expect(context.distinct(:years).sort).to eq([ 25, 30 ])
417
455
  end
418
456
  end
419
457
 
@@ -2325,7 +2363,7 @@ describe Mongoid::Contextual::Mongo do
2325
2363
  end
2326
2364
 
2327
2365
  it 'creates a pipeline with the selector as one of the $match criteria' do
2328
- expect(pipeline_match).to include({ :'$text' => { :'$search' => "New Order" } })
2366
+ expect(pipeline_match).to include({ '$text' => { '$search' => "New Order" } })
2329
2367
  end
2330
2368
 
2331
2369
  it 'creates a pipeline with the $exists operator as one of the $match criteria' do