mongoid 6.3.0 → 6.4.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/config/locales/en.yml +21 -0
- data/lib/mongoid.rb +1 -1
- data/lib/mongoid/clients.rb +2 -0
- data/lib/mongoid/clients/sessions.rb +113 -0
- data/lib/mongoid/clients/storage_options.rb +1 -0
- data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
- data/lib/mongoid/contextual/map_reduce.rb +6 -2
- data/lib/mongoid/contextual/memory.rb +7 -2
- data/lib/mongoid/contextual/mongo.rb +11 -2
- data/lib/mongoid/criteria.rb +1 -0
- data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
- data/lib/mongoid/errors.rb +1 -0
- data/lib/mongoid/errors/invalid_session_use.rb +24 -0
- data/lib/mongoid/indexable.rb +4 -4
- data/lib/mongoid/persistable.rb +1 -1
- data/lib/mongoid/persistable/creatable.rb +4 -2
- data/lib/mongoid/persistable/deletable.rb +4 -2
- data/lib/mongoid/persistable/destroyable.rb +1 -5
- data/lib/mongoid/persistable/updatable.rb +2 -2
- data/lib/mongoid/persistable/upsertable.rb +2 -1
- data/lib/mongoid/relations/embedded/batchable.rb +10 -4
- data/lib/mongoid/relations/many.rb +4 -0
- data/lib/mongoid/relations/referenced/many.rb +1 -1
- data/lib/mongoid/relations/touchable.rb +1 -1
- data/lib/mongoid/reloadable.rb +1 -1
- data/lib/mongoid/tasks/database.rb +3 -2
- data/lib/mongoid/threaded.rb +38 -0
- data/lib/mongoid/version.rb +1 -1
- data/spec/mongoid/attributes/nested_spec.rb +4 -0
- data/spec/mongoid/clients/sessions_spec.rb +325 -0
- data/spec/mongoid/contextual/mongo_spec.rb +38 -0
- data/spec/mongoid/criteria/queryable/selectable_spec.rb +32 -3
- data/spec/mongoid/interceptable_spec.rb +1 -1
- data/spec/mongoid/persistable/deletable_spec.rb +19 -0
- data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
- data/spec/mongoid/persistable_spec.rb +16 -16
- data/spec/spec_helper.rb +70 -0
- metadata +17 -7
- metadata.gz.sig +0 -0
@@ -21,7 +21,8 @@ module Mongoid
|
|
21
21
|
# @since 3.0.0
|
22
22
|
def upsert(options = {})
|
23
23
|
prepare_upsert(options) do
|
24
|
-
collection.find(atomic_selector).update_one(
|
24
|
+
collection.find(atomic_selector).update_one(
|
25
|
+
as_attributes, upsert: true, session: session)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
@@ -38,7 +38,8 @@ module Mongoid
|
|
38
38
|
pre_process_batch_remove(docs, :delete)
|
39
39
|
unless docs.empty?
|
40
40
|
collection.find(selector).update_one(
|
41
|
-
positionally(selector, "$unset" => { path => true })
|
41
|
+
positionally(selector, "$unset" => { path => true }),
|
42
|
+
session: session
|
42
43
|
)
|
43
44
|
post_process_batch_remove(docs, :delete)
|
44
45
|
end
|
@@ -58,7 +59,8 @@ module Mongoid
|
|
58
59
|
removals = pre_process_batch_remove(docs, method)
|
59
60
|
if !docs.empty?
|
60
61
|
collection.find(selector).update_one(
|
61
|
-
positionally(selector, "$pullAll" => { path => removals })
|
62
|
+
positionally(selector, "$pullAll" => { path => removals }),
|
63
|
+
session: session
|
62
64
|
)
|
63
65
|
post_process_batch_remove(docs, method)
|
64
66
|
end
|
@@ -133,7 +135,9 @@ module Mongoid
|
|
133
135
|
inserts = pre_process_batch_insert(docs)
|
134
136
|
if insertable?
|
135
137
|
collection.find(selector).update_one(
|
136
|
-
positionally(selector, '$set' => { path => inserts })
|
138
|
+
positionally(selector, '$set' => { path => inserts }),
|
139
|
+
session: session
|
140
|
+
)
|
137
141
|
post_process_batch_insert(docs)
|
138
142
|
end
|
139
143
|
inserts
|
@@ -156,7 +160,9 @@ module Mongoid
|
|
156
160
|
pushes = pre_process_batch_insert(docs)
|
157
161
|
if insertable?
|
158
162
|
collection.find(selector).update_one(
|
159
|
-
positionally(selector, '$push' => { path => { '$each' => pushes } })
|
163
|
+
positionally(selector, '$push' => { path => { '$each' => pushes } }),
|
164
|
+
session: session
|
165
|
+
)
|
160
166
|
post_process_batch_insert(docs)
|
161
167
|
end
|
162
168
|
pushes
|
@@ -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
|
data/lib/mongoid/reloadable.rb
CHANGED
@@ -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.
|
@@ -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
|
data/lib/mongoid/threaded.rb
CHANGED
@@ -325,5 +325,43 @@ module Mongoid
|
|
325
325
|
def validations_for(klass)
|
326
326
|
validations[klass] ||= []
|
327
327
|
end
|
328
|
+
|
329
|
+
# Cache a session for this thread.
|
330
|
+
#
|
331
|
+
# @example Save a session for this thread.
|
332
|
+
# Threaded.set_session(session)
|
333
|
+
#
|
334
|
+
# @param [ Mongo::Session ] session The session to save.
|
335
|
+
#
|
336
|
+
# @since 6.4.0
|
337
|
+
def set_session(session)
|
338
|
+
Thread.current[:session] = session
|
339
|
+
end
|
340
|
+
|
341
|
+
# Get the cached session for this thread.
|
342
|
+
#
|
343
|
+
# @example Get the session for this thread.
|
344
|
+
# Threaded.get_session
|
345
|
+
#
|
346
|
+
# @return [ Mongo::Session, nil ] The session cached on this thread or nil.
|
347
|
+
#
|
348
|
+
# @since 6.4.0
|
349
|
+
def get_session
|
350
|
+
Thread.current[:session]
|
351
|
+
end
|
352
|
+
|
353
|
+
# Clear the cached session for this thread.
|
354
|
+
#
|
355
|
+
# @example Clear this thread's session.
|
356
|
+
# Threaded.clear_session
|
357
|
+
#
|
358
|
+
# @return [ nil ]
|
359
|
+
#
|
360
|
+
# @since 6.4.0
|
361
|
+
def clear_session
|
362
|
+
session = get_session
|
363
|
+
session.end_session if session
|
364
|
+
Thread.current[:session] = nil
|
365
|
+
end
|
328
366
|
end
|
329
367
|
end
|
data/lib/mongoid/version.rb
CHANGED
@@ -0,0 +1,325 @@
|
|
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
|
+
Mongoid::Clients.with_name(:other).instance_variable_get(:@monitoring).subscribers['Command'].find do |s|
|
20
|
+
s.is_a?(EventSubscriber)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:insert_events) do
|
25
|
+
subscriber.started_events.select { |event| event.command_name == :insert }
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:update_events) do
|
29
|
+
subscriber.started_events.select { |event| event.command_name == :update }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when a session is used on a model class' do
|
33
|
+
|
34
|
+
context 'when sessions are supported', if: sessions_supported? do
|
35
|
+
|
36
|
+
around do |example|
|
37
|
+
Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
|
38
|
+
subscriber.clear_events!
|
39
|
+
Person.with(client: :other) do
|
40
|
+
example.run
|
41
|
+
end
|
42
|
+
Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when another thread is started' do
|
46
|
+
|
47
|
+
let!(:last_use_diff) do
|
48
|
+
Person.with_session do |session|
|
49
|
+
Person.create
|
50
|
+
Person.create
|
51
|
+
last_use = session.instance_variable_get(:@server_session).last_use
|
52
|
+
Thread.new { Person.create }.value
|
53
|
+
session.instance_variable_get(:@server_session).last_use - last_use
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'does not use the session for that thread' do
|
58
|
+
expect(Person.count).to be(2)
|
59
|
+
expect(Person.with(client: :default) { Person.count }).to be(1)
|
60
|
+
lsids_sent = insert_events.collect { |event| event.command['lsid'] }
|
61
|
+
expect(lsids_sent.size).to eq(2)
|
62
|
+
expect(lsids_sent.uniq.size).to eq(1)
|
63
|
+
expect(last_use_diff).to eq(0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when the operations in the session block are all on the class' do
|
68
|
+
|
69
|
+
before do
|
70
|
+
Person.with_session do
|
71
|
+
Person.create
|
72
|
+
Person.create
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'uses a single session id for all operations on the class' do
|
77
|
+
expect(Person.count).to be(2)
|
78
|
+
lsids_sent = insert_events.collect { |event| event.command['lsid'] }
|
79
|
+
expect(lsids_sent.size).to eq(2)
|
80
|
+
expect(lsids_sent.uniq.size).to eq(1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when the operations in the session block are also on another class' do
|
85
|
+
|
86
|
+
context 'when the other class uses the same client' do
|
87
|
+
|
88
|
+
before do
|
89
|
+
Post.with(client: :other) do
|
90
|
+
Person.with_session do
|
91
|
+
Person.create
|
92
|
+
Person.create
|
93
|
+
Post.create
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'uses a single session id for all operations on the class' do
|
99
|
+
expect(Post.with(client: :other) { |klass| klass.count }).to be(1)
|
100
|
+
lsids_sent = insert_events.collect { |event| event.command['lsid'] }
|
101
|
+
expect(lsids_sent.size).to eq(3)
|
102
|
+
expect(lsids_sent.uniq.size).to eq(1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when the other class uses a different client' do
|
107
|
+
|
108
|
+
let!(:error) do
|
109
|
+
e = nil
|
110
|
+
begin
|
111
|
+
Person.with_session do
|
112
|
+
Person.create
|
113
|
+
Person.create
|
114
|
+
Post.create
|
115
|
+
end
|
116
|
+
rescue => ex
|
117
|
+
e = ex
|
118
|
+
end
|
119
|
+
e
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'raises an error' do
|
123
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'uses a single session id for all operations on the class' do
|
127
|
+
expect(Person.count).to be(2)
|
128
|
+
lsids_sent = insert_events.collect { |event| event.command['lsid'] }
|
129
|
+
expect(lsids_sent.size).to eq(2)
|
130
|
+
expect(lsids_sent.uniq.size).to eq(1)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when sessions are nested' do
|
135
|
+
|
136
|
+
let!(:error) do
|
137
|
+
e = nil
|
138
|
+
begin
|
139
|
+
Person.with_session do
|
140
|
+
Person.with_session do
|
141
|
+
Person.create
|
142
|
+
Post.create
|
143
|
+
end
|
144
|
+
end
|
145
|
+
rescue => ex
|
146
|
+
e = ex
|
147
|
+
end
|
148
|
+
e
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'raises an error' do
|
152
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'does not execute any operations' do
|
156
|
+
expect(Person.count).to be(0)
|
157
|
+
expect(insert_events).to be_empty
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when sessions are not supported', unless: sessions_supported? do
|
164
|
+
|
165
|
+
let!(:error) do
|
166
|
+
e = nil
|
167
|
+
begin
|
168
|
+
Person.with_session {}
|
169
|
+
rescue => ex
|
170
|
+
e = ex
|
171
|
+
end
|
172
|
+
e
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'raises a sessions not supported error' do
|
176
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
177
|
+
expect(error.message).to include('not supported')
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'when a session is used on a model instance' do
|
183
|
+
|
184
|
+
let!(:person) do
|
185
|
+
Person.with(client: :other) do |klass|
|
186
|
+
klass.create
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when sessions are supported', if: sessions_supported? do
|
191
|
+
|
192
|
+
around do |example|
|
193
|
+
Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
|
194
|
+
subscriber.clear_events!
|
195
|
+
person.with(client: :other) do
|
196
|
+
example.run
|
197
|
+
end
|
198
|
+
Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when the operations in the session block are all on the instance' do
|
202
|
+
|
203
|
+
before do
|
204
|
+
person.with_session do
|
205
|
+
person.username = 'Emily'
|
206
|
+
person.save
|
207
|
+
person.age = 80
|
208
|
+
person.save
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'uses a single session id for all operations on the class' do
|
213
|
+
expect(person.reload.username).to eq('Emily')
|
214
|
+
expect(person.reload.age).to eq(80)
|
215
|
+
lsids_sent = update_events.collect { |event| event.command['lsid'] }
|
216
|
+
expect(lsids_sent.size).to eq(2)
|
217
|
+
expect(lsids_sent.uniq.size).to eq(1)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'when the operations in the session block are also on another class' do
|
222
|
+
|
223
|
+
context 'when the other class uses the same client' do
|
224
|
+
|
225
|
+
before do
|
226
|
+
Post.with(client: :other) do
|
227
|
+
person.with_session do
|
228
|
+
person.username = 'Emily'
|
229
|
+
person.save
|
230
|
+
person.posts << Post.create
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'uses a single session id for all operations on the class' do
|
236
|
+
expect(person.reload.username).to eq('Emily')
|
237
|
+
expect(Post.with(client: :other) { Post.count }).to be(1)
|
238
|
+
update_lsids_sent = update_events.collect { |event| event.command['lsid'] }
|
239
|
+
expect(update_lsids_sent.size).to eq(3) # person update, counter cache, post assignment
|
240
|
+
expect(update_lsids_sent.uniq.size).to eq(1) # person update, counter cache, post assignment
|
241
|
+
insert_lsids_sent = insert_events.collect { |event| event.command['lsid'] }
|
242
|
+
expect(insert_lsids_sent.size).to eq(2)
|
243
|
+
expect(insert_lsids_sent.uniq.size).to eq(1)
|
244
|
+
expect(update_lsids_sent.uniq).to eq(insert_lsids_sent.uniq)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'when the other class uses a different client' do
|
249
|
+
|
250
|
+
let!(:error) do
|
251
|
+
e = nil
|
252
|
+
begin
|
253
|
+
person.with_session do
|
254
|
+
person.username = 'Emily'
|
255
|
+
person.save
|
256
|
+
person.posts << Post.create
|
257
|
+
end
|
258
|
+
rescue => ex
|
259
|
+
e = ex
|
260
|
+
end
|
261
|
+
e
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'raises an error' do
|
265
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'uses a single session id for all operations on the class' do
|
269
|
+
expect(person.reload.username).to eq('Emily')
|
270
|
+
expect(Post.count).to be(0)
|
271
|
+
update_lsids_sent = update_events.collect { |event| event.command['lsid'] }
|
272
|
+
expect(update_lsids_sent.size).to eq(1)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'when sessions are nested' do
|
277
|
+
|
278
|
+
let!(:error) do
|
279
|
+
e = nil
|
280
|
+
begin
|
281
|
+
person.with_session do
|
282
|
+
person.with_session do
|
283
|
+
person.username = 'Emily'
|
284
|
+
person.save
|
285
|
+
person.posts << Post.create
|
286
|
+
end
|
287
|
+
end
|
288
|
+
rescue => ex
|
289
|
+
e = ex
|
290
|
+
end
|
291
|
+
e
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'raises an error' do
|
295
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'does not execute any operations' do
|
299
|
+
expect(person.reload.username).not_to eq('Emily')
|
300
|
+
expect(Post.count).to be(0)
|
301
|
+
expect(update_events).to be_empty
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'when sessions are not supported', unless: sessions_supported? do
|
308
|
+
|
309
|
+
let!(:error) do
|
310
|
+
e = nil
|
311
|
+
begin
|
312
|
+
person.with_session {}
|
313
|
+
rescue => ex
|
314
|
+
e = ex
|
315
|
+
end
|
316
|
+
e
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'raises a sessions not supported error' do
|
320
|
+
expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
|
321
|
+
expect(error.message).to include('not supported')
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|