mongoid 8.0.6 → 8.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51995917ca452899edfb1e432aecad879c476f89057c5377e55081a5c2a989ac
4
- data.tar.gz: b01e8f7141b1f37d0ddfc51b5e4fa889f09b85988069af8cc8af7214a00a888c
3
+ metadata.gz: 5ffe4422a33c5d676a4ae8e28c2d3ea748a738022b5fe0defa69a675fefbff8d
4
+ data.tar.gz: d296900948d3e569beb33f4c63876ac24c45368dd0f3000efebecdcaed2cfbcf
5
5
  SHA512:
6
- metadata.gz: 7a011a2367c77d3e7abdc57a3781f45c97c71451afcaae7a3358a3e0173e8d3bc4ef09ef92d32f8d72c14ca89b5d8f3d9fccdf8b0a7b84ccb9a874329413b561
7
- data.tar.gz: a868fc0217fc98cccfa8b6c3f9c0dccb4425354ddb4f23a1819f52dc539e2754e8dd8a508a804e05bddeeb8be73fe09ff6f027d141a22d3f479c3adc63097fcb
6
+ metadata.gz: 3632cdca025ba56792b041d07a560d00f3a561fa21ca9b0f44167fbf54b14fe0cc5ae040adeff7a24ac98795f6a54cfbfcd88ae3057906a44d606573e18ee398
7
+ data.tar.gz: 243a702300881cc8ef82f7937e8ff3ea718b2c23aea8768a6392d47b6f3f841dba062e0133d142855b9f9ccf85f3bfbe0f45b743daed7f82813e02f6b9a1674b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -142,6 +142,19 @@ module Mongoid
142
142
  end
143
143
  end
144
144
 
145
+ # When this flag is true, callbacks for embedded documents will not be
146
+ # called. This is the default in 8.x, but will be changed to false in 9.0.
147
+ #
148
+ # Setting this flag to true (as it is in 8.x) may lead to stack
149
+ # overflow errors if there are more than cicrca 1000 embedded
150
+ # documents in the root document's dependencies graph.
151
+ #
152
+ # It is strongly recommended to set this flag to false in 8.x, if you
153
+ # are not using around callbacks for embedded documents.
154
+ #
155
+ # See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
156
+ option :around_callbacks_for_embeds, default: true
157
+
145
158
  # Has Mongoid been configured? This is checking that at least a valid
146
159
  # client config exists.
147
160
  #
@@ -56,7 +56,12 @@ module Mongoid
56
56
  # @return [ Integer ] The number of matches.
57
57
  def count(options = {}, &block)
58
58
  return super(&block) if block_given?
59
- view.count_documents(options)
59
+
60
+ if valid_for_count_documents?
61
+ view.count_documents(options)
62
+ else
63
+ view.count(options)
64
+ end
60
65
  end
61
66
 
62
67
  # Get the estimated number of documents matching the query.
@@ -813,6 +818,24 @@ module Mongoid
813
818
  docs = eager_load(docs)
814
819
  limit ? docs : docs.first
815
820
  end
821
+
822
+ # Queries whether the current context is valid for use with
823
+ # the #count_documents? predicate. A context is valid if it
824
+ # does not include a `$where` operator.
825
+ #
826
+ # @return [ true | false ] whether or not the current context
827
+ # excludes a `$where` operator.
828
+ def valid_for_count_documents?(hash = view.filter)
829
+ # Note that `view.filter` is a BSON::Document, and all keys in a
830
+ # BSON::Document are strings; we don't need to worry about symbol
831
+ # representations of `$where`.
832
+ hash.keys.each do |key|
833
+ return false if key == '$where'
834
+ return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
835
+ end
836
+
837
+ true
838
+ end
816
839
  end
817
840
  end
818
841
  end
@@ -24,10 +24,11 @@ module Mongoid
24
24
  # #=> Mongoid.logger.warn("meow is deprecated and will be removed from Mongoid 8.0 (eat :catnip instead)")
25
25
  #
26
26
  # @param [ Module ] target_module The parent which contains the method.
27
- # @param [ Symbol | Hash<Symbol, [ Symbol | String ]> ] method_descriptors
27
+ # @param [ [ Symbol | Hash<Symbol, [ Symbol | String ]> ]... ] *method_descriptors
28
28
  # The methods to deprecate, with optional replacement instructions.
29
29
  def deprecate(target_module, *method_descriptors)
30
- Mongoid::Deprecation.deprecate_methods(target_module, *method_descriptors)
30
+ @_deprecator ||= Mongoid::Deprecation.new
31
+ @_deprecator.deprecate_methods(target_module, *method_descriptors)
31
32
  end
32
33
  end
33
34
  end
@@ -15,10 +15,10 @@ module Mongoid
15
15
  #
16
16
  # @return Array<Proc> The deprecation behavior.
17
17
  def behavior
18
- @behavior ||= Array(->(message, callstack, _deprecation_horizon, _gem_name) {
18
+ @behavior ||= Array(->(*args) {
19
19
  logger = Mongoid.logger
20
- logger.warn(message)
21
- logger.debug(callstack.join("\n ")) if debug
20
+ logger.warn(args[0])
21
+ logger.debug(args[1].join("\n ")) if debug
22
22
  })
23
23
  end
24
24
  end
@@ -43,7 +43,7 @@ module Mongoid
43
43
  real_key = klass.database_field_name(key2)
44
44
 
45
45
  value.delete(key2) if real_key != key2
46
- value[real_key] = (key == "$rename") ? value2.to_s : mongoize_for(key, klass, real_key, value2)
46
+ value[real_key] = value_for(key, klass, real_key, value2)
47
47
  end
48
48
  consolidated[key] ||= {}
49
49
  consolidated[key].update(value)
@@ -185,6 +185,24 @@ module Mongoid
185
185
 
186
186
  private
187
187
 
188
+ # Get the value for the provided operator, klass, key and value.
189
+ #
190
+ # This is necessary for special cases like $rename, $addToSet and $push.
191
+ #
192
+ # @param [ String ] operator The operator.
193
+ # @param [ Class ] klass The model class.
194
+ # @param [ String | Symbol ] key The field key.
195
+ # @param [ Object ] value The original value.
196
+ #
197
+ # @return [ Object ] Value prepared for the provided operator.
198
+ def value_for(operator, klass, key, value)
199
+ case operator
200
+ when "$rename" then value.to_s
201
+ when "$addToSet", "$push" then value.mongoize
202
+ else mongoize_for(operator, klass, operator, value)
203
+ end
204
+ end
205
+
188
206
  # Mongoize for the klass, key and value.
189
207
  #
190
208
  # @api private
@@ -79,7 +79,7 @@ module Mongoid
79
79
  # @example Run only the after save callbacks.
80
80
  # model.run_after_callbacks(:save)
81
81
  #
82
- # @param [ Array<Symbol> ] kinds The events that are occurring.
82
+ # @param [ Symbol... ] *kinds The events that are occurring.
83
83
  #
84
84
  # @return [ Object ] The result of the chain executing.
85
85
  def run_after_callbacks(*kinds)
@@ -96,7 +96,7 @@ module Mongoid
96
96
  # @example Run only the before save callbacks.
97
97
  # model.run_before_callbacks(:save, :create)
98
98
  #
99
- # @param [ Array<Symbol> ] kinds The events that are occurring.
99
+ # @param [ Symbol... ] *kinds The events that are occurring.
100
100
  #
101
101
  # @return [ Object ] The result of the chain executing.
102
102
  def run_before_callbacks(*kinds)
@@ -134,36 +134,126 @@ module Mongoid
134
134
  # Run the callbacks for embedded documents.
135
135
  #
136
136
  # @param [ Symbol ] kind The type of callback to execute.
137
- # @param [ Array<Document> ] children Children to exeute callbacks on. If
137
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
138
138
  # nil, callbacks will be executed on all cascadable children of
139
139
  # the document.
140
140
  #
141
141
  # @api private
142
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
143
+ if Mongoid::Config.around_callbacks_for_embeds
144
+ _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
145
+ else
146
+ _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
147
+ end
148
+ end
149
+
150
+ # Execute the callbacks of given kind for embedded documents including
151
+ # around callbacks.
152
+ #
153
+ # @note This method is prone to stack overflow errors if the document
154
+ # has a large number of embedded documents. It is recommended to avoid
155
+ # using around callbacks for embedded documents until a proper solution
156
+ # is implemented.
157
+ #
158
+ # @param [ Symbol ] kind The type of callback to execute.
159
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
160
+ # nil, callbacks will be executed on all cascadable children of
161
+ # the document.
162
+ #
163
+ # @api private
164
+ def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
143
165
  child, *tail = (children || cascadable_children(kind))
144
166
  if child.nil?
145
- return block&.call
167
+ block&.call
146
168
  elsif tail.empty?
147
- return child.run_callbacks(child_callback_type(kind, child), &block)
169
+ child.run_callbacks(child_callback_type(kind, child), &block)
148
170
  else
149
- return child.run_callbacks(child_callback_type(kind, child)) do
150
- _mongoid_run_child_callbacks(kind, children: tail, &block)
171
+ child.run_callbacks(child_callback_type(kind, child)) do
172
+ _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
151
173
  end
152
174
  end
153
175
  end
154
176
 
155
- # This is used to store callbacks to be executed later. A good use case for
156
- # this is delaying the after_find and after_initialize callbacks until the
157
- # associations are set on the document. This can also be used to delay
158
- # applying the defaults on a document.
177
+ # Execute the callbacks of given kind for embedded documents without
178
+ # around callbacks.
179
+ #
180
+ # @param [ Symbol ] kind The type of callback to execute.
181
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
182
+ # nil, callbacks will be executed on all cascadable children of
183
+ # the document.
184
+ #
185
+ # @api private
186
+ def _mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
187
+ children = (children || cascadable_children(kind))
188
+ callback_list = _mongoid_run_child_before_callbacks(kind, children: children)
189
+ return false if callback_list == false
190
+ value = block&.call
191
+ callback_list.each do |_next_sequence, env|
192
+ env.value &&= value
193
+ end
194
+ return false if _mongoid_run_child_after_callbacks(callback_list: callback_list) == false
195
+
196
+ value
197
+ end
198
+
199
+ # Execute the before callbacks of given kind for embedded documents.
200
+ #
201
+ # @param [ Symbol ] kind The type of callback to execute.
202
+ # @param [ Array<Document> ] children Children to execute callbacks on.
203
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
204
+ # pairs of callback sequence and environment. This list will be later used
205
+ # to execute after callbacks in reverse order.
159
206
  #
160
- # @return [ Array<Symbol> ] an array of symbols that represent the pending callbacks.
207
+ # @api private
208
+ def _mongoid_run_child_before_callbacks(kind, children: [], callback_list: [])
209
+ children.each do |child|
210
+ chain = child.__callbacks[child_callback_type(kind, child)]
211
+ env = ActiveSupport::Callbacks::Filters::Environment.new(child, false, nil)
212
+ next_sequence = compile_callbacks(chain)
213
+ unless next_sequence.final?
214
+ Mongoid.logger.warn("Around callbacks are disabled for embedded documents. Skipping around callbacks for #{child.class.name}.")
215
+ Mongoid.logger.warn("To enable around callbacks for embedded documents, set Mongoid::Config.around_callbacks_for_embeds to true.")
216
+ end
217
+ next_sequence.invoke_before(env)
218
+ return false if env.halted
219
+ env.value = !env.halted
220
+ callback_list << [next_sequence, env]
221
+ if (grandchildren = child.send(:cascadable_children, kind))
222
+ _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
223
+ end
224
+ end
225
+ callback_list
226
+ end
227
+
228
+ # Execute the after callbacks.
229
+ #
230
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
231
+ # pairs of callback sequence and environment.
232
+ def _mongoid_run_child_after_callbacks(callback_list: [])
233
+ callback_list.reverse_each do |next_sequence, env|
234
+ next_sequence.invoke_after(env)
235
+ return false if env.halted
236
+ end
237
+ end
238
+
239
+ # Returns the stored callbacks to be executed later.
240
+ #
241
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
161
242
  #
162
243
  # @api private
163
244
  def pending_callbacks
164
245
  @pending_callbacks ||= [].to_set
165
246
  end
166
247
 
248
+ # Stores callbacks to be executed later. A good use case for
249
+ # this is delaying the after_find and after_initialize callbacks until the
250
+ # associations are set on the document. This can also be used to delay
251
+ # applying the defaults on a document.
252
+ #
253
+ # @param [ Array<Symbol> ] value Method symbols of the pending callbacks to store.
254
+ #
255
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
256
+ #
167
257
  # @api private
168
258
  def pending_callbacks=(value)
169
259
  @pending_callbacks = value
@@ -298,7 +388,7 @@ module Mongoid
298
388
  end
299
389
  self.class.send :define_method, name do
300
390
  env = ActiveSupport::Callbacks::Filters::Environment.new(self, false, nil)
301
- sequence = chain.compile
391
+ sequence = compile_callbacks(chain)
302
392
  sequence.invoke_before(env)
303
393
  env.value = !env.halted
304
394
  sequence.invoke_after(env)
@@ -308,5 +398,24 @@ module Mongoid
308
398
  end
309
399
  send(name)
310
400
  end
401
+
402
+ # Compile the callback chain.
403
+ #
404
+ # This method hides the differences between ActiveSupport implementations
405
+ # before and after 7.1.
406
+ #
407
+ # @param [ ActiveSupport::Callbacks::CallbackChain ] chain The callback chain.
408
+ # @param [ Symbol | nil ] type The type of callback chain to compile.
409
+ #
410
+ # @return [ ActiveSupport::Callbacks::CallbackSequence ] The compiled callback sequence.
411
+ def compile_callbacks(chain, type = nil)
412
+ if chain.method(:compile).arity == 0
413
+ # ActiveSupport < 7.1
414
+ chain.compile
415
+ else
416
+ # ActiveSupport >= 7.1
417
+ chain.compile(type)
418
+ end
419
+ end
311
420
  end
312
421
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.0.6"
4
+ VERSION = "8.0.7"
5
5
  end
@@ -447,4 +447,24 @@ describe 'callbacks integration tests' do
447
447
  expect(saved_person.previously_persisted_value).to be_truthy
448
448
  end
449
449
  end
450
+
451
+ context 'cascade callbacks' do
452
+ ruby_version_gte '3.0'
453
+ config_override :around_callbacks_for_embeds, false
454
+
455
+ let(:book) do
456
+ Book.new
457
+ end
458
+
459
+ before do
460
+ 1500.times do
461
+ book.pages.build
462
+ end
463
+ end
464
+
465
+ # https://jira.mongodb.org/browse/MONGOID-5658
466
+ it 'does not raise SystemStackError' do
467
+ expect { book.save! }.not_to raise_error(SystemStackError)
468
+ end
469
+ end
450
470
  end
@@ -608,7 +608,7 @@ describe Mongoid::Config do
608
608
 
609
609
  it 'passes uuid to driver' do
610
610
  Mongo::Client.should receive(:new).with(SpecConfig.instance.addresses,
611
- auto_encryption_options: {
611
+ { auto_encryption_options: {
612
612
  'key_vault_namespace' => 'admin.datakeys',
613
613
  'kms_providers' => {'local' => {'key' => 'z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U'}},
614
614
  'schema_map' => {'blog_development.comments' => {
@@ -625,7 +625,7 @@ describe Mongoid::Config do
625
625
  platform: "mongoid-#{Mongoid::VERSION}",
626
626
  wrapping_libraries: [
627
627
  {'name' => 'Mongoid', 'version' => Mongoid::VERSION},
628
- ],
628
+ ]},
629
629
  )
630
630
 
631
631
  client
@@ -158,6 +158,16 @@ describe Mongoid::Contextual::Mongo do
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ context 'when for_js is present' do
163
+ let(:context) do
164
+ Band.for_js('this.name == "Depeche Mode"')
165
+ end
166
+
167
+ it 'counts the expected records' do
168
+ expect(context.count).to eq(1)
169
+ end
170
+ end
161
171
  end
162
172
 
163
173
  describe "#estimated_count" do
@@ -3563,16 +3573,51 @@ describe Mongoid::Contextual::Mongo do
3563
3573
 
3564
3574
  context "when the attributes are in the correct type" do
3565
3575
 
3566
- before do
3567
- context.update_all("$set" => { name: "Smiths" })
3576
+ context "when operation is $set" do
3577
+
3578
+ before do
3579
+ context.update_all("$set" => { name: "Smiths" })
3580
+ end
3581
+
3582
+ it "updates the first matching document" do
3583
+ expect(depeche_mode.reload.name).to eq("Smiths")
3584
+ end
3585
+
3586
+ it "updates the last matching document" do
3587
+ expect(new_order.reload.name).to eq("Smiths")
3588
+ end
3568
3589
  end
3569
3590
 
3570
- it "updates the first matching document" do
3571
- expect(depeche_mode.reload.name).to eq("Smiths")
3591
+ context "when operation is $push" do
3592
+
3593
+ before do
3594
+ depeche_mode.update_attribute(:genres, ["electronic"])
3595
+ new_order.update_attribute(:genres, ["electronic"])
3596
+ context.update_all("$push" => { genres: "pop" })
3597
+ end
3598
+
3599
+ it "updates the first matching document" do
3600
+ expect(depeche_mode.reload.genres).to eq(["electronic", "pop"])
3601
+ end
3602
+
3603
+ it "updates the last matching document" do
3604
+ expect(new_order.reload.genres).to eq(["electronic", "pop"])
3605
+ end
3572
3606
  end
3573
3607
 
3574
- it "updates the last matching document" do
3575
- expect(new_order.reload.name).to eq("Smiths")
3608
+ context "when operation is $addToSet" do
3609
+
3610
+ before do
3611
+ context.update_all("$addToSet" => { genres: "electronic" })
3612
+ end
3613
+
3614
+ it "updates the first matching document" do
3615
+ expect(depeche_mode.reload.genres).to eq(["electronic"])
3616
+ end
3617
+
3618
+ it "updates the last matching document" do
3619
+ expect(new_order.reload.genres).to eq(["electronic"])
3620
+ end
3576
3621
  end
3577
3622
  end
3578
3623
 
@@ -90,7 +90,7 @@ describe Mongoid::Copyable do
90
90
  end
91
91
 
92
92
  it 'calls constructor with explicitly declared attributes only' do
93
- expect(Mongoid::Factory).to receive(:build).with(cls, 'name' => 'test').and_call_original
93
+ expect(Mongoid::Factory).to receive(:build).with(cls, { 'name' => 'test' }).and_call_original
94
94
  cloned
95
95
  end
96
96
  end