mongoid 7.0.1 → 7.0.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongoid/association.rb +0 -1
  5. data/lib/mongoid/association/depending.rb +22 -1
  6. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +3 -3
  7. data/lib/mongoid/association/referenced/has_many/proxy.rb +1 -1
  8. data/lib/mongoid/association/relatable.rb +16 -2
  9. data/lib/mongoid/attributes/nested.rb +14 -3
  10. data/lib/mongoid/contextual/map_reduce.rb +1 -1
  11. data/lib/mongoid/copyable.rb +3 -2
  12. data/lib/mongoid/document.rb +2 -0
  13. data/lib/mongoid/matchable.rb +3 -0
  14. data/lib/mongoid/matchable/nor.rb +37 -0
  15. data/lib/mongoid/persistable/settable.rb +58 -13
  16. data/lib/mongoid/persistence_context.rb +5 -1
  17. data/lib/mongoid/touchable.rb +102 -0
  18. data/lib/mongoid/version.rb +1 -1
  19. data/spec/app/models/array_field.rb +7 -0
  20. data/spec/app/models/updatable.rb +7 -0
  21. data/spec/mongoid/association/accessors_spec.rb +39 -0
  22. data/spec/mongoid/association/depending_spec.rb +253 -0
  23. data/spec/mongoid/association/polymorphic_spec.rb +59 -0
  24. data/spec/mongoid/association/referenced/belongs_to_spec.rb +3 -3
  25. data/spec/mongoid/association/referenced/has_one_spec.rb +59 -0
  26. data/spec/mongoid/attributes/nested_spec.rb +18 -2
  27. data/spec/mongoid/clients/factory_spec.rb +3 -3
  28. data/spec/mongoid/clients/options_spec.rb +28 -13
  29. data/spec/mongoid/clients/sessions_spec.rb +3 -3
  30. data/spec/mongoid/clients/transactions_spec.rb +369 -0
  31. data/spec/mongoid/copyable_spec.rb +16 -0
  32. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  33. data/spec/mongoid/matchable_spec.rb +26 -1
  34. data/spec/mongoid/persistable/settable_spec.rb +128 -9
  35. data/spec/mongoid/{association/touchable_spec.rb → touchable_spec.rb} +28 -7
  36. data/spec/spec_helper.rb +8 -0
  37. metadata +457 -448
  38. metadata.gz.sig +0 -0
  39. data/lib/mongoid/association/touchable.rb +0 -97
@@ -12,10 +12,12 @@ describe Mongoid::Attributes::Nested do
12
12
 
13
13
  before do
14
14
  Person.accepts_nested_attributes_for :favorites
15
+ Person.accepts_nested_attributes_for :children
15
16
  end
16
17
 
17
18
  after do
18
19
  Person.send(:undef_method, :favorites_attributes=)
20
+ Person.send(:undef_method, :children_attributes=)
19
21
  Person.nested_attributes.clear
20
22
  end
21
23
 
@@ -23,9 +25,18 @@ describe Mongoid::Attributes::Nested do
23
25
  expect(person).to respond_to(:favorites_attributes=)
24
26
  end
25
27
 
28
+ it "does not autosave if the association is embedded" do
29
+ expect(person).not_to respond_to(:autosave_documents_for_favorites)
30
+ end
31
+
32
+ it "autosaves if the association is not embedded" do
33
+ expect(person).to respond_to(:autosave_documents_for_children)
34
+ end
35
+
26
36
  it "adds the method name to the nested attributes list" do
27
37
  expect(Person.nested_attributes).to eq({
28
- "favorites_attributes" => "favorites_attributes="
38
+ "favorites_attributes" => "favorites_attributes=",
39
+ "children_attributes" => "children_attributes="
29
40
  })
30
41
  end
31
42
  end
@@ -202,7 +213,12 @@ describe Mongoid::Attributes::Nested do
202
213
  context "when the relation is a referenced in" do
203
214
 
204
215
  before do
205
- Post.accepts_nested_attributes_for :person
216
+ Post.accepts_nested_attributes_for :person, autosave: false
217
+ end
218
+
219
+ after do
220
+ Post.send(:undef_method, :person_attributes=)
221
+ Post.nested_attributes.clear
206
222
  end
207
223
 
208
224
  let(:post) do
@@ -132,8 +132,8 @@ describe Mongoid::Clients::Factory do
132
132
 
133
133
  let(:config) do
134
134
  {
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" }
135
+ default: { hosts: [ "127.0.0.1:1234" ], database: database_id },
136
+ secondary: { uri: "mongodb://127.0.0.1:1234,127.0.0.1:5678/mongoid_test" }
137
137
  }
138
138
  end
139
139
 
@@ -162,7 +162,7 @@ describe Mongoid::Clients::Factory do
162
162
  end
163
163
 
164
164
  it "sets the cluster's seeds" do
165
- expect(seeds).to eq([ "127.0.0.1:27017", "127.0.0.1:27018" ])
165
+ expect(seeds).to eq([ "127.0.0.1:1234", "127.0.0.1:5678" ])
166
166
  end
167
167
  end
168
168
  end
@@ -188,25 +188,40 @@ describe Mongoid::Clients::Options do
188
188
 
189
189
  context 'when returning a criteria' do
190
190
 
191
- let(:context_and_criteria) do
192
- collection = nil
193
- cxt = TestModel.with(read: :secondary) do |klass|
194
- collection = klass.all.collection
195
- klass.persistence_context
191
+ shared_context 'applies secondary read preference' do
192
+
193
+ let(:context_and_criteria) do
194
+ collection = nil
195
+ cxt = TestModel.with(read_secondary_option) do |klass|
196
+ collection = klass.all.collection
197
+ klass.persistence_context
198
+ end
199
+ [ cxt, collection ]
196
200
  end
197
- [ cxt, collection ]
198
- end
199
201
 
200
- let(:persistence_context) do
201
- context_and_criteria[0]
202
+ let(:persistence_context) do
203
+ context_and_criteria[0]
204
+ end
205
+
206
+ let(:client) do
207
+ context_and_criteria[1].client
208
+ end
209
+
210
+ it 'applies the options to the criteria client' do
211
+ expect(client.options['read']).to eq('mode' => :secondary)
212
+ end
202
213
  end
203
214
 
204
- let(:client) do
205
- context_and_criteria[1].client
215
+ context 'read: :secondary shorthand' do
216
+ let(:read_secondary_option) { {read: :secondary} }
217
+
218
+ it_behaves_like 'applies secondary read preference'
206
219
  end
207
220
 
208
- it 'applies the options to the criteria client' do
209
- expect(client.options['read']).to eq(:secondary)
221
+ context 'read: {mode: :secondary}' do
222
+ let(:read_secondary_option) { {read: {mode: :secondary}} }
223
+
224
+ it_behaves_like 'applies secondary read preference'
210
225
  end
211
226
  end
212
227
 
@@ -16,17 +16,17 @@ describe Mongoid::Clients::Sessions do
16
16
  end
17
17
 
18
18
  let(:subscriber) do
19
- Mongoid::Clients.with_name(:other).instance_variable_get(:@monitoring).subscribers['Command'].find do |s|
19
+ Mongoid::Clients.with_name(:other).send(:monitoring).subscribers['Command'].find do |s|
20
20
  s.is_a?(EventSubscriber)
21
21
  end
22
22
  end
23
23
 
24
24
  let(:insert_events) do
25
- subscriber.started_events.select { |event| event.command_name == :insert }
25
+ subscriber.started_events.select { |event| event.command_name == 'insert' }
26
26
  end
27
27
 
28
28
  let(:update_events) do
29
- subscriber.started_events.select { |event| event.command_name == :update }
29
+ subscriber.started_events.select { |event| event.command_name == 'update' }
30
30
  end
31
31
 
32
32
  context 'when a session is used on a model class' do
@@ -0,0 +1,369 @@
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).send(: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(:insert_events_txn_numbers) do
29
+ insert_events.map { |i| i.instance_variable_get(:@integer) }
30
+ end
31
+
32
+ let(:update_events) do
33
+ subscriber.started_events.select { |event| event.command_name == 'update' }
34
+ end
35
+
36
+ let(:update_events_txn_numbers) do
37
+ update_events.map { |i| i.instance_variable_get(:@integer) }
38
+ end
39
+
40
+ let(:other_events) do
41
+ subscriber.started_events.reject { |event| ['insert', 'update'].include?(event.command_name) }
42
+ end
43
+
44
+ context 'when a transaction is used on a model class' do
45
+
46
+ context 'when transactions are supported', if: testing_transactions? do
47
+
48
+ around do |example|
49
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
50
+ Mongoid::Clients.with_name(:other).command(create: :people)
51
+ Mongoid::Clients.with_name(:other).command(create: :posts)
52
+ subscriber.clear_events!
53
+ Person.with(client: :other) do
54
+ example.run
55
+ end
56
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
57
+ end
58
+
59
+ context 'when another thread is started' do
60
+
61
+ let!(:last_use_diff) do
62
+ Person.with_session do |s|
63
+ s.start_transaction
64
+ Person.create
65
+ Person.create
66
+ Thread.new { Person.create }.value
67
+ s.commit_transaction
68
+ end
69
+ end
70
+
71
+ it 'does not use the transaction for that thread' do
72
+ expect(Person.count).to be(2)
73
+ expect(Person.with(client: :default) { Person.count }).to be(1)
74
+ expect(insert_events.count { |e| e.command['startTransaction'] }).to be(1)
75
+ expect(other_events.count { |e| e.command_name == 'commitTransaction' }).to be(1)
76
+ end
77
+ end
78
+
79
+ context 'when the operations in the transactions block are all on the class' do
80
+
81
+ before do
82
+ Person.with_session do |s|
83
+ s.start_transaction
84
+ Person.create
85
+ Person.create
86
+ s.commit_transaction
87
+ end
88
+ end
89
+
90
+ it 'uses a single transaction number for all operations on the class' do
91
+ expect(Person.count).to be(2)
92
+ expect(insert_events_txn_numbers.size).to eq(2)
93
+ expect(insert_events_txn_numbers.uniq.size).to eq(1)
94
+ end
95
+ end
96
+
97
+ context 'when the operations in the transactions block are also on another class' do
98
+
99
+ context 'when the other class uses the same client' do
100
+
101
+ before do
102
+ Post.with(client: :other) do
103
+ Person.with_session do |s|
104
+ s.start_transaction
105
+ Person.create
106
+ Person.create
107
+ Post.create
108
+ s.commit_transaction
109
+ end
110
+ end
111
+ end
112
+
113
+ it 'uses a single transaction number for all operations on the class' do
114
+ expect(Post.with(client: :other) { |klass| klass.count }).to be(1)
115
+ expect(insert_events_txn_numbers.size).to eq(3)
116
+ expect(insert_events_txn_numbers.uniq.size).to eq(1)
117
+ end
118
+ end
119
+
120
+ context 'when the other class uses a different client' do
121
+
122
+ let!(:error) do
123
+ e = nil
124
+ begin
125
+ Person.with_session do |s|
126
+ s.start_transaction
127
+ Person.create
128
+ Person.create
129
+ Post.create
130
+ s.commit_transaction
131
+ end
132
+ rescue => ex
133
+ e = ex
134
+ end
135
+ e
136
+ end
137
+
138
+ it 'raises an error' do
139
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
140
+ end
141
+
142
+ it 'aborted the transaction' do
143
+ expect(Person.count).to be(0)
144
+ expect(Post.count).to be(0)
145
+ expect(insert_events_txn_numbers.size).to eq(2)
146
+ expect(other_events.count { |e| e.command_name == 'abortTransaction'}).to be(1)
147
+ expect(other_events.count { |e| e.command_name == 'commitTransaction'}).to be(0)
148
+ end
149
+ end
150
+
151
+ context 'when transactions are nested' do
152
+
153
+ let!(:error) do
154
+ e = nil
155
+ begin
156
+ Person.with_session do |s|
157
+ s.start_transaction
158
+ s.start_transaction
159
+ Person.create
160
+ Post.create
161
+ s.commit_transaction
162
+ end
163
+ rescue => ex
164
+ e = ex
165
+ end
166
+ e
167
+ end
168
+
169
+ it 'raises an error' do
170
+ expect(error).to be_a(Mongo::Error::InvalidTransactionOperation)
171
+ end
172
+
173
+ it 'does not execute any operations' do
174
+ expect(Person.count).to be(0)
175
+ expect(Post.count).to be(0)
176
+ expect(insert_events).to be_empty
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ context 'when sessions are supported but transactions are not', if: sessions_supported? && !testing_transactions? do
183
+
184
+ let!(:error) do
185
+ e = nil
186
+ begin
187
+ Person.with_session do |s|
188
+ s.start_transaction
189
+ Person.create
190
+ s.commit_transaction
191
+ end
192
+ rescue => ex
193
+ e = ex
194
+ end
195
+ e
196
+ end
197
+
198
+ it 'raises a transactions not supported error' do
199
+ expect(Person.count).to eq(0)
200
+ expect(error).to be_a(Mongo::Error::OperationFailure)
201
+ end
202
+ end
203
+ end
204
+
205
+ context 'when a transaction is used on a model instance' do
206
+
207
+ let!(:person) do
208
+ Person.with(client: :other) do |klass|
209
+ klass.create
210
+ end
211
+ end
212
+
213
+ context 'when transactions are supported', if: testing_transactions? do
214
+
215
+ around do |example|
216
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
217
+ Mongoid::Clients.with_name(:other).command(create: :people)
218
+ Mongoid::Clients.with_name(:other).command(create: :posts)
219
+ subscriber.clear_events!
220
+ person.with(client: :other) do
221
+ example.run
222
+ end
223
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
224
+ end
225
+
226
+ context 'when the operations in the transaction block are all on the instance' do
227
+
228
+ before do
229
+ person.with_session do |s|
230
+ s.start_transaction
231
+ person.username = 'Emily'
232
+ person.save
233
+ person.age = 80
234
+ person.save
235
+ s.commit_transaction
236
+ end
237
+ end
238
+
239
+ it 'uses a single transaction number for all operations on the class' do
240
+ expect(person.reload.username).to eq('Emily')
241
+ expect(person.reload.age).to eq(80)
242
+ expect(update_events_txn_numbers.size).to eq(2)
243
+ expect(update_events_txn_numbers.uniq.size).to eq(1)
244
+ end
245
+ end
246
+
247
+ context 'when the operations in the transaction block are also on another class' do
248
+
249
+ context 'when the other class uses the same client' do
250
+
251
+ before do
252
+ Post.with(client: :other) do
253
+ person.with_session do |s|
254
+ s.start_transaction
255
+ person.username = 'Emily'
256
+ person.save
257
+ person.posts << Post.create
258
+ s.commit_transaction
259
+ end
260
+ end
261
+ end
262
+
263
+ it 'uses a single transaction number for all operations on the class' do
264
+ expect(person.reload.username).to eq('Emily')
265
+ expect(Post.with(client: :other) { Post.count }).to be(1)
266
+ expect(update_events_txn_numbers.size).to eq(3) # person update, counter cache, post assignment
267
+ expect(update_events_txn_numbers.uniq.size).to eq(1) # person update, counter cache, post assignment
268
+ expect(insert_events_txn_numbers.size).to eq(2)
269
+ expect(insert_events_txn_numbers.uniq.size).to eq(1)
270
+ expect(update_events_txn_numbers.uniq).to eq(insert_events_txn_numbers.uniq)
271
+ end
272
+ end
273
+
274
+ context 'when the other class uses a different client' do
275
+
276
+ let!(:error) do
277
+ e = nil
278
+ begin
279
+ person.with_session do |s|
280
+ s.start_transaction
281
+ person.username = 'Emily'
282
+ person.save
283
+ person.posts << Post.create
284
+ s.commit_transaction
285
+ end
286
+ rescue => ex
287
+ e = ex
288
+ end
289
+ e
290
+ end
291
+
292
+ it 'raises an error' do
293
+ expect(error).to be_a(Mongoid::Errors::InvalidSessionUse)
294
+ end
295
+
296
+ it 'aborted the transction' do
297
+ expect(person.reload.username).not_to eq('Emily')
298
+ expect(Post.count).to be(0)
299
+ expect(update_events_txn_numbers.size).to eq(1)
300
+ expect(insert_events_txn_numbers.size).to eq(1)
301
+ end
302
+ end
303
+
304
+ context 'when transactions are nested' do
305
+
306
+ let!(:error) do
307
+ e = nil
308
+ begin
309
+ person.with_session do |s|
310
+ s.start_transaction
311
+ s.start_transaction
312
+ person.username = 'Emily'
313
+ person.save
314
+ person.posts << Post.create
315
+ s.commit_transaction
316
+ end
317
+ rescue => ex
318
+ e = ex
319
+ end
320
+ e
321
+ end
322
+
323
+ it 'raises an error' do
324
+ expect(error).to be_a(Mongo::Error::InvalidTransactionOperation)
325
+ end
326
+
327
+ it 'does not execute any operations' do
328
+ expect(person.reload.username).not_to eq('Emily')
329
+ expect(Post.count).to be(0)
330
+ expect(update_events).to be_empty
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ context 'when sessions are supported but transactions are not', if: sessions_supported? && !testing_transactions? do
337
+
338
+ around do |example|
339
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
340
+ Mongoid::Clients.with_name(:other).command(create: :people)
341
+ subscriber.clear_events!
342
+ person.with(client: :other) do
343
+ example.run
344
+ end
345
+ Mongoid::Clients.with_name(:other).database.collections.each(&:drop)
346
+ end
347
+
348
+ let!(:error) do
349
+ e = nil
350
+ begin
351
+ person.with_session do |s|
352
+ s.start_transaction
353
+ person.username = 'Emily'
354
+ person.save
355
+ s.commit_transaction
356
+ end
357
+ rescue => ex
358
+ e = ex
359
+ end
360
+ e
361
+ end
362
+
363
+ it 'raises a sessions not supported error' do
364
+ expect(person.reload.username).not_to be('Emily')
365
+ expect(error).to be_a(Mongo::Error::OperationFailure)
366
+ end
367
+ end
368
+ end
369
+ end