mongoid 8.0.3 → 8.0.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 (38) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Rakefile +25 -0
  4. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +17 -15
  5. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +4 -0
  6. data/lib/mongoid/association/referenced/has_many/proxy.rb +4 -0
  7. data/lib/mongoid/atomic.rb +7 -0
  8. data/lib/mongoid/changeable.rb +1 -3
  9. data/lib/mongoid/criteria/queryable/extensions/array.rb +1 -1
  10. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -1
  11. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +0 -8
  12. data/lib/mongoid/criteria/queryable/extensions/string.rb +1 -11
  13. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +0 -10
  14. data/lib/mongoid/criteria/translator.rb +45 -0
  15. data/lib/mongoid/criteria.rb +1 -0
  16. data/lib/mongoid/document.rb +50 -13
  17. data/lib/mongoid/factory.rb +21 -8
  18. data/lib/mongoid/matcher.rb +21 -6
  19. data/lib/mongoid/reloadable.rb +3 -5
  20. data/lib/mongoid/shardable.rb +35 -11
  21. data/lib/mongoid/threaded.rb +30 -0
  22. data/lib/mongoid/traversable.rb +1 -1
  23. data/lib/mongoid/version.rb +1 -1
  24. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +37 -32
  25. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +143 -197
  26. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +102 -114
  27. data/spec/mongoid/attributes_spec.rb +2 -2
  28. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +0 -59
  29. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  30. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -0
  31. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  32. data/spec/mongoid/reloadable_spec.rb +24 -0
  33. data/spec/mongoid/shardable_models.rb +14 -0
  34. data/spec/mongoid/shardable_spec.rb +153 -61
  35. data/spec/mongoid_spec.rb +1 -1
  36. data.tar.gz.sig +0 -0
  37. metadata +656 -648
  38. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 684d53a067a1e3410535a0dc06db2b587c5e43a19c85af0defe7f29c81e1c74d
4
- data.tar.gz: c285640352d90d219d7ccd6c0ce6c90fdf1b800f853d2aa44898025c9d49ccf8
3
+ metadata.gz: 53bba3a9611a89eaf77478beacbf4a8a3c6751503ecf1919407c7af05e134af0
4
+ data.tar.gz: ebe8a6705c4c035e99720d1a37e8103948aa1cb61a66ab42b0d426dd7e10c135
5
5
  SHA512:
6
- metadata.gz: 6a13a92d079784bfc190d1ebddff3f2bdd6f0d3d2fb0cee336856ba8e870199b83cc7c809d17cb62a913a64624630ffd397bfa45779503b107b97f82fb4be615
7
- data.tar.gz: b887e765c135fe7018863908afabe9128007e85628cbcf11bc971cabf06a162833239da37d32293b1c3565838cf9ad14898fd91ac510a29bf37779b782411ee7
6
+ metadata.gz: 15c3ecad78d34f2bdc3967042c0175b639d813aa44bcb2e6a5aa4ee5ec6d3adcb13e428accefb0edfa6b7dab59710829194e00cc17cb0f38ff5673957c97e6ce
7
+ data.tar.gz: ba5f2a0a8cd2ddc48650d9e7b67c162d4071f254ea9de03070308a411aeda8db2ac40c3a548d9543bed2bad637aa001e2a4ed0fe039cb380a30a69d6b79164fd
checksums.yaml.gz.sig CHANGED
Binary file
data/Rakefile CHANGED
@@ -11,6 +11,15 @@ $: << File.join(ROOT, 'spec/shared/lib')
11
11
  require "rake"
12
12
  require "rspec/core/rake_task"
13
13
  require 'mrss/spec_organizer'
14
+ require 'rubygems/package'
15
+ require 'rubygems/security/policies'
16
+
17
+ def signed_gem?(path_to_gem)
18
+ Gem::Package.new(path_to_gem, Gem::Security::HighSecurity).verify
19
+ true
20
+ rescue Gem::Security::Exception => e
21
+ false
22
+ end
14
23
 
15
24
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
16
25
  require "mongoid/version"
@@ -103,3 +112,19 @@ namespace :release do
103
112
  end
104
113
  end
105
114
  end
115
+
116
+ desc 'Verifies that all built gems in pkg/ are valid'
117
+ task :verify do
118
+ gems = Dir['pkg/*.gem']
119
+ if gems.empty?
120
+ puts 'There are no gems in pkg/ to verify'
121
+ else
122
+ gems.each do |gem|
123
+ if signed_gem?(gem)
124
+ puts "#{gem} is signed"
125
+ else
126
+ abort "#{gem} is not signed"
127
+ end
128
+ end
129
+ end
130
+ end
@@ -154,6 +154,23 @@ module Mongoid
154
154
  end
155
155
  end
156
156
 
157
+ # Mongoid::Extensions::Array defines Array#delete_one, so we need
158
+ # to make sure that method behaves reasonably on proxies, too.
159
+ alias delete_one delete
160
+
161
+ # Removes a single document from the collection *in memory only*.
162
+ # It will *not* persist the change.
163
+ #
164
+ # @param [ Document ] document The document to delete.
165
+ #
166
+ # @api private
167
+ def _remove(document)
168
+ _target.delete_one(document)
169
+ _unscoped.delete_one(document)
170
+ update_attributes_hash
171
+ reindex
172
+ end
173
+
157
174
  # Delete all the documents in the association without running callbacks.
158
175
  #
159
176
  # @example Delete all documents from the association.
@@ -397,21 +414,6 @@ module Mongoid
397
414
  _association.criteria(_base, _target)
398
415
  end
399
416
 
400
- # Deletes one document from the target and unscoped.
401
- #
402
- # @api private
403
- #
404
- # @example Delete one document.
405
- # relation.delete_one(doc)
406
- #
407
- # @param [ Document ] document The document to delete.
408
- def delete_one(document)
409
- _target.delete_one(document)
410
- _unscoped.delete_one(document)
411
- update_attributes_hash
412
- reindex
413
- end
414
-
415
417
  # Integrate the document into the association. will set its metadata and
416
418
  # attempt to bind the inverse.
417
419
  #
@@ -137,6 +137,10 @@ module Mongoid
137
137
  doc
138
138
  end
139
139
 
140
+ # Mongoid::Extensions::Array defines Array#delete_one, so we need
141
+ # to make sure that method behaves reasonably on proxies, too.
142
+ alias delete_one delete
143
+
140
144
  # Removes all associations between the base document and the target
141
145
  # documents by deleting the foreign keys and the references, orphaning
142
146
  # the target documents in the process.
@@ -105,6 +105,10 @@ module Mongoid
105
105
  end
106
106
  end
107
107
 
108
+ # Mongoid::Extensions::Array defines Array#delete_one, so we need
109
+ # to make sure that method behaves reasonably on proxies, too.
110
+ alias delete_one delete
111
+
108
112
  # Deletes all related documents from the database given the supplied
109
113
  # conditions.
110
114
  #
@@ -311,6 +311,13 @@ module Mongoid
311
311
 
312
312
  private
313
313
 
314
+ # Clears all pending atomic updates.
315
+ def reset_atomic_updates!
316
+ Atomic::UPDATES.each do |update|
317
+ send(update).clear
318
+ end
319
+ end
320
+
314
321
  # Generates the atomic updates in the correct order.
315
322
  #
316
323
  # @example Generate the updates.
@@ -70,9 +70,7 @@ module Mongoid
70
70
  def move_changes
71
71
  @previous_changes = changes
72
72
  @previous_attributes = attributes.dup
73
- Atomic::UPDATES.each do |update|
74
- send(update).clear
75
- end
73
+ reset_atomic_updates!
76
74
  changed_attributes.clear
77
75
  end
78
76
 
@@ -105,7 +105,7 @@ module Mongoid
105
105
  #
106
106
  # @return [ Hash ] The field/direction pair.
107
107
  def __sort_pair__
108
- { first => last.to_direction }
108
+ { first => Mongoid::Criteria::Translator.to_direction(last) }
109
109
  end
110
110
 
111
111
  private
@@ -115,7 +115,7 @@ module Mongoid
115
115
  def __sort_option__
116
116
  tap do |hash|
117
117
  hash.each_pair do |key, value|
118
- hash.store(key, value.to_direction)
118
+ hash.store(key, Mongoid::Criteria::Translator.to_direction(value))
119
119
  end
120
120
  end
121
121
  end
@@ -30,14 +30,6 @@ module Mongoid
30
30
  ::Time.at(self).utc
31
31
  end
32
32
 
33
- # Get the integer as a sort direction.
34
- #
35
- # @example Get the integer as a sort direction.
36
- # 1.to_direction
37
- #
38
- # @return [ Integer ] self.
39
- def to_direction; self; end
40
-
41
33
  module ClassMethods
42
34
 
43
35
  # Get the object as a numeric.
@@ -49,7 +49,7 @@ module Mongoid
49
49
  split(/,/).inject({}) do |hash, spec|
50
50
  hash.tap do |_hash|
51
51
  field, direction = spec.strip.split(/\s/)
52
- _hash[field.to_sym] = direction.to_direction
52
+ _hash[field.to_sym] = Mongoid::Criteria::Translator.to_direction(direction)
53
53
  end
54
54
  end
55
55
  end
@@ -67,16 +67,6 @@ module Mongoid
67
67
  ::String.__expr_part__(self, value, negating)
68
68
  end
69
69
 
70
- # Get the string as a sort direction.
71
- #
72
- # @example Get the string as a sort direction.
73
- # "1".to_direction
74
- #
75
- # @return [ Integer ] The direction.
76
- def to_direction
77
- self =~ /desc/i ? -1 : 1
78
- end
79
-
80
70
  module ClassMethods
81
71
 
82
72
  # Get the value as a expression.
@@ -21,16 +21,6 @@ module Mongoid
21
21
  ::String.__expr_part__(self, value, negating)
22
22
  end
23
23
 
24
- # Get the symbol as a sort direction.
25
- #
26
- # @example Get the symbol as a sort direction.
27
- # "1".to_direction
28
- #
29
- # @return [ Integer ] The direction.
30
- def to_direction
31
- to_s.to_direction
32
- end
33
-
34
24
  module ClassMethods
35
25
 
36
26
  # Adds a method on symbol as a convenience for the MongoDB operator.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ class Criteria
5
+
6
+ # This is a helper module for translating atomic and composite
7
+ # Ruby values into corresponding query and option components.
8
+ # Originally implemented as patches to core classes, that approach
9
+ # has generally fallen into disfavor, as it bleeds too much into
10
+ # the public namespace.
11
+ #
12
+ # @api private
13
+ module Translator
14
+ extend self
15
+
16
+ # Converts the given value to a direction specification for use in
17
+ # sorting.
18
+ #
19
+ # @example Convert the value to a direction.
20
+ # Translator.to_direction(:desc)
21
+ # Translator.to_direction("1")
22
+ # Translator.to_direction(-1)
23
+ # Translator.to_direction(score: { "$meta": "textScore" })
24
+ #
25
+ # @param [ Hash | Numeric | String | Symbol ] value The value to convert.
26
+ #
27
+ # @return [ Hash | Numeric ] The direction.
28
+ def to_direction(value)
29
+ case value
30
+ when Hash then
31
+ value
32
+ when Numeric then
33
+ value
34
+ when String then
35
+ value =~ /desc/i ? -1 : 1
36
+ when Symbol then
37
+ to_direction(value.to_s)
38
+ else
39
+ raise ArgumentError, "cannot translate #{value.inspect} (#{value.class}) to a direction specification"
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -8,6 +8,7 @@ require "mongoid/criteria/modifiable"
8
8
  require "mongoid/criteria/queryable"
9
9
  require "mongoid/criteria/scopable"
10
10
  require "mongoid/criteria/options"
11
+ require "mongoid/criteria/translator"
11
12
 
12
13
  module Mongoid
13
14
 
@@ -101,7 +101,7 @@ module Mongoid
101
101
  #
102
102
  # @return [ Document ] A new document.
103
103
  def initialize(attrs = nil, &block)
104
- construct_document(attrs, execute_callbacks: true, &block)
104
+ construct_document(attrs, &block)
105
105
  end
106
106
 
107
107
  # Return the model name of the document.
@@ -208,13 +208,22 @@ module Mongoid
208
208
  # Does the construction of a document.
209
209
  #
210
210
  # @param [ Hash ] attrs The attributes to set up the document with.
211
- # @param [ true | false ] execute_callbacks Flag specifies whether callbacks
212
- # should be run.
211
+ # @param [ Hash ] options The options to use.
212
+ #
213
+ # @option options [ true | false ] :execute_callbacks Flag specifies
214
+ # whether callbacks should be run.
213
215
  #
214
216
  # @return [ Document ] A new document.
215
217
  #
218
+ # @note A Ruby 2.x bug prevents the options hash from being keyword
219
+ # arguments. Once we drop support for Ruby 2.x, we can reimplement
220
+ # the options hash as keyword arguments.
221
+ # See https://bugs.ruby-lang.org/issues/15753
222
+ #
216
223
  # @api private
217
- def construct_document(attrs = nil, execute_callbacks: true)
224
+ def construct_document(attrs = nil, options = {})
225
+ execute_callbacks = options.fetch(:execute_callbacks, Threaded.execute_callbacks?)
226
+
218
227
  @__parent = nil
219
228
  _building do
220
229
  @new_record = true
@@ -281,6 +290,20 @@ module Mongoid
281
290
 
282
291
  module ClassMethods
283
292
 
293
+ # Indicate whether callbacks should be invoked by default or not,
294
+ # within the block. Callbacks may always be explicitly invoked by passing
295
+ # `execute_callbacks: true` where available.
296
+ #
297
+ # @params execute_callbacks [ true | false ] Whether callbacks should be
298
+ # suppressed or not.
299
+ def with_callbacks(execute_callbacks)
300
+ saved, Threaded.execute_callbacks =
301
+ Threaded.execute_callbacks?, execute_callbacks
302
+ yield
303
+ ensure
304
+ Threaded.execute_callbacks = saved
305
+ end
306
+
284
307
  # Instantiate a new object, only when loaded from the database or when
285
308
  # the attributes have already been typecast.
286
309
  #
@@ -295,7 +318,7 @@ module Mongoid
295
318
  #
296
319
  # @return [ Document ] A new document.
297
320
  def instantiate(attrs = nil, selected_fields = nil, &block)
298
- instantiate_document(attrs, selected_fields, execute_callbacks: true, &block)
321
+ instantiate_document(attrs, selected_fields, &block)
299
322
  end
300
323
 
301
324
  # Instantiate the document.
@@ -303,13 +326,20 @@ module Mongoid
303
326
  # @param [ Hash ] attrs The hash of attributes to instantiate with.
304
327
  # @param [ Integer ] selected_fields The selected fields from the
305
328
  # criteria.
306
- # @param [ true | false ] execute_callbacks Flag specifies whether callbacks
307
- # should be run.
329
+ # @param [ Hash ] options The options to use.
330
+ #
331
+ # @option options [ true | false ] :execute_callbacks Flag specifies
332
+ # whether callbacks should be run.
308
333
  #
309
334
  # @return [ Document ] A new document.
310
335
  #
336
+ # @note A Ruby 2.x bug prevents the options hash from being keyword
337
+ # arguments. Once we drop support for Ruby 2.x, we can reimplement
338
+ # the options hash as keyword arguments.
339
+ #
311
340
  # @api private
312
- def instantiate_document(attrs = nil, selected_fields = nil, execute_callbacks: true)
341
+ def instantiate_document(attrs = nil, selected_fields = nil, options = {})
342
+ execute_callbacks = options.fetch(:execute_callbacks, Threaded.execute_callbacks?)
313
343
  attributes = if Mongoid.legacy_attributes
314
344
  attrs
315
345
  else
@@ -340,15 +370,22 @@ module Mongoid
340
370
  # Allocates and constructs a document.
341
371
  #
342
372
  # @param [ Hash ] attrs The attributes to set up the document with.
343
- # @param [ true | false ] execute_callbacks Flag specifies whether callbacks
344
- # should be run.
373
+ # @param [ Hash ] options The options to use.
374
+ #
375
+ # @option options [ true | false ] :execute_callbacks Flag specifies
376
+ # whether callbacks should be run.
377
+ #
378
+ # @note A Ruby 2.x bug prevents the options hash from being keyword
379
+ # arguments. Once we drop support for Ruby 2.x, we can reimplement
380
+ # the options hash as keyword arguments.
381
+ # See https://bugs.ruby-lang.org/issues/15753
345
382
  #
346
383
  # @return [ Document ] A new document.
347
384
  #
348
385
  # @api private
349
- def construct_document(attrs = nil, execute_callbacks: true)
350
- doc = allocate
351
- doc.send(:construct_document, attrs, execute_callbacks: execute_callbacks)
386
+ def construct_document(attrs = nil, options = {})
387
+ execute_callbacks = options.fetch(:execute_callbacks, Threaded.execute_callbacks?)
388
+ with_callbacks(execute_callbacks) { new(attrs) }
352
389
  end
353
390
 
354
391
  # Returns all types to query for when using this class as the base.
@@ -25,27 +25,40 @@ module Mongoid
25
25
  #
26
26
  # @return [ Document ] The instantiated document.
27
27
  def build(klass, attributes = nil)
28
- execute_build(klass, attributes, execute_callbacks: true)
28
+ # A bug in Ruby 2.x (including 2.7.7) causes the attributes hash to be
29
+ # interpreted as keyword arguments, because execute_build accepts
30
+ # a keyword argument. Forcing an empty set of keyword arguments works
31
+ # around the bug. Once Ruby 2.x support is dropped, this hack can be
32
+ # removed.
33
+ # See https://bugs.ruby-lang.org/issues/15753
34
+ execute_build(klass, attributes)
29
35
  end
30
36
 
31
37
  # Execute the build.
32
38
  #
33
39
  # @param [ Class ] klass The class to instantiate from if _type is not present.
34
40
  # @param [ Hash ] attributes The document attributes.
35
- # @param [ true | false ] execute_callbacks Flag specifies whether callbacks
36
- # should be run.
41
+ # @param [ Hash ] options The options to use.
42
+ #
43
+ # @option options [ true | false ] :execute_callbacks Flag specifies
44
+ # whether callbacks should be run.
45
+ #
46
+ # @note A Ruby 2.x bug prevents the options hash from being keyword
47
+ # arguments. Once we drop support for Ruby 2.x, we can reimplement
48
+ # the options hash as keyword arguments.
49
+ # See https://bugs.ruby-lang.org/issues/15753
37
50
  #
38
51
  # @return [ Document ] The instantiated document.
39
52
  #
40
53
  # @api private
41
- def execute_build(klass, attributes = nil, execute_callbacks: true)
54
+ def execute_build(klass, attributes = nil, options = {})
42
55
  attributes ||= {}
43
56
  dvalue = attributes[klass.discriminator_key] || attributes[klass.discriminator_key.to_sym]
44
57
  type = klass.get_discriminator_mapping(dvalue)
45
58
  if type
46
- type.construct_document(attributes, execute_callbacks: execute_callbacks)
59
+ type.construct_document(attributes, options)
47
60
  else
48
- klass.construct_document(attributes, execute_callbacks: execute_callbacks)
61
+ klass.construct_document(attributes, options)
49
62
  end
50
63
  end
51
64
 
@@ -76,7 +89,7 @@ module Mongoid
76
89
  #
77
90
  # @return [ Document ] The instantiated document.
78
91
  def from_db(klass, attributes = nil, criteria = nil, selected_fields = nil)
79
- execute_from_db(klass, attributes, criteria, selected_fields, execute_callbacks: true)
92
+ execute_from_db(klass, attributes, criteria, selected_fields)
80
93
  end
81
94
 
82
95
  # Execute from_db.
@@ -97,7 +110,7 @@ module Mongoid
97
110
  # @return [ Document ] The instantiated document.
98
111
  #
99
112
  # @api private
100
- def execute_from_db(klass, attributes = nil, criteria = nil, selected_fields = nil, execute_callbacks: true)
113
+ def execute_from_db(klass, attributes = nil, criteria = nil, selected_fields = nil, execute_callbacks: Threaded.execute_callbacks?)
101
114
  if criteria
102
115
  selected_fields ||= criteria.options[:fields]
103
116
  end
@@ -2,7 +2,6 @@ module Mongoid
2
2
 
3
3
  # @api private
4
4
  module Matcher
5
-
6
5
  # Extracts field values in the document at the specified key.
7
6
  #
8
7
  # The document can be a Hash or a model instance.
@@ -45,7 +44,7 @@ module Mongoid
45
44
  # If a document has hash fields, as_attributes would keep those fields
46
45
  # as Hash instances which do not offer indifferent access.
47
46
  # Convert to BSON::Document to get indifferent access on hash fields.
48
- document = BSON::Document.new(document.send(:as_attributes))
47
+ document = document.send(:as_attributes)
49
48
  end
50
49
 
51
50
  current = [document]
@@ -55,8 +54,9 @@ module Mongoid
55
54
  current.each do |doc|
56
55
  case doc
57
56
  when Hash
58
- if doc.key?(field)
59
- new << doc[field]
57
+ actual_key = find_exact_key(doc, field)
58
+ if !actual_key.nil?
59
+ new << doc[actual_key]
60
60
  end
61
61
  when Array
62
62
  if (index = field.to_i).to_s == field
@@ -66,8 +66,9 @@ module Mongoid
66
66
  end
67
67
  doc.each do |subdoc|
68
68
  if Hash === subdoc
69
- if subdoc.key?(field)
70
- new << subdoc[field]
69
+ actual_key = find_exact_key(subdoc, field)
70
+ if !actual_key.nil?
71
+ new << subdoc[actual_key]
71
72
  end
72
73
  end
73
74
  end
@@ -79,6 +80,20 @@ module Mongoid
79
80
 
80
81
  current
81
82
  end
83
+
84
+ # Indifferent string or symbol key lookup, returning the exact key.
85
+ #
86
+ # @param [ Hash ] hash The input hash.
87
+ # @param [ String | Symbol ] key The key to perform indifferent lookups with.
88
+ #
89
+ # @return [ String | Symbol | nil ] The exact key (with the correct type) that exists in the hash, or nil if the key does not exist.
90
+ module_function def find_exact_key(hash, key)
91
+ key_s = key.to_s
92
+ return key_s if hash.key?(key_s)
93
+
94
+ key_sym = key.to_sym
95
+ hash.key?(key_sym) ? key_sym : nil
96
+ end
82
97
  end
83
98
  end
84
99
 
@@ -16,16 +16,14 @@ module Mongoid
16
16
  #
17
17
  # @return [ Document ] The document, reloaded.
18
18
  def reload
19
- if @atomic_selector
20
- # Clear atomic_selector cache for sharded clusters. MONGOID-5076
21
- remove_instance_variable('@atomic_selector')
22
- end
23
-
24
19
  reloaded = _reload
25
20
  if Mongoid.raise_not_found_error && (reloaded.nil? || reloaded.empty?)
26
21
  shard_keys = atomic_selector.with_indifferent_access.slice(*shard_key_fields, :_id)
27
22
  raise Errors::DocumentNotFound.new(self.class, _id, shard_keys)
28
23
  end
24
+
25
+ reset_atomic_updates!
26
+
29
27
  @attributes = reloaded
30
28
  @attributes_before_type_cast = @attributes.dup
31
29
  @changed_attributes = {}
@@ -47,18 +47,22 @@ module Mongoid
47
47
  self.class.shard_key_fields
48
48
  end
49
49
 
50
- # Returns the selector that would match the current version of this
51
- # document.
50
+ # Returns the selector that would match the defined shard keys. If
51
+ # `prefer_persisted` is false (the default), it uses the current values
52
+ # of the specified shard keys, otherwise, it will try to use whatever value
53
+ # was most recently persisted.
54
+ #
55
+ # @param [ true | false ] prefer_persisted Whether to use the current
56
+ # value of the shard key fields, or to use their most recently persisted
57
+ # values.
52
58
  #
53
59
  # @return [ Hash ] The shard key selector.
54
60
  #
55
61
  # @api private
56
- def shard_key_selector
57
- selector = {}
58
- shard_key_fields.each do |field|
59
- selector[field.to_s] = send(field)
62
+ def shard_key_selector(prefer_persisted: false)
63
+ shard_key_fields.each_with_object({}) do |field, selector|
64
+ selector[field.to_s] = shard_key_field_value(field.to_s, prefer_persisted: prefer_persisted)
60
65
  end
61
- selector
62
66
  end
63
67
 
64
68
  # Returns the selector that would match the existing version of this
@@ -72,11 +76,31 @@ module Mongoid
72
76
  #
73
77
  # @api private
74
78
  def shard_key_selector_in_db
75
- selector = {}
76
- shard_key_fields.each do |field|
77
- selector[field.to_s] = new_record? ? send(field) : attribute_was(field)
79
+ shard_key_selector(prefer_persisted: true)
80
+ end
81
+
82
+ # Returns the value for the named shard key. If the field identifies
83
+ # an embedded document, the key will be parsed and recursively evaluated.
84
+ # If `prefer_persisted` is true, the value last persisted to the database
85
+ # will be returned, regardless of what the current value of the attribute
86
+ # may be.
87
+ #
88
+ # @param [String] field The name of the field to evaluate
89
+ # @param [ true|false ] prefer_persisted Whether or not to prefer the
90
+ # persisted value over the current value.
91
+ #
92
+ # @return [ Object ] The value of the named field.
93
+ #
94
+ # @api private
95
+ def shard_key_field_value(field, prefer_persisted:)
96
+ if field.include?(".")
97
+ relation, remaining = field.split(".", 2)
98
+ send(relation)&.shard_key_field_value(remaining, prefer_persisted: prefer_persisted)
99
+ elsif prefer_persisted && !new_record?
100
+ attribute_was(field)
101
+ else
102
+ send(field)
78
103
  end
79
- selector
80
104
  end
81
105
 
82
106
  module ClassMethods
@@ -26,6 +26,10 @@ module Mongoid
26
26
  hash[key] = "[mongoid]:#{key}-stack"
27
27
  end
28
28
 
29
+ # The key storing the default value for whether or not callbacks are
30
+ # executed on documents.
31
+ EXECUTE_CALLBACKS = '[mongoid]:execute-callbacks'
32
+
29
33
  extend self
30
34
 
31
35
  # Begin entry into a named thread local stack.
@@ -346,5 +350,31 @@ module Mongoid
346
350
  session.end_session if session
347
351
  Thread.current["[mongoid]:session"] = nil
348
352
  end
353
+
354
+ # Queries whether document callbacks should be executed by default for the
355
+ # current thread.
356
+ #
357
+ # Unless otherwise indicated (by #execute_callbacks=), this will return
358
+ # true.
359
+ #
360
+ # @return [ true | false ] Whether or not document callbacks should be
361
+ # executed by default.
362
+ def execute_callbacks?
363
+ if Thread.current.key?(EXECUTE_CALLBACKS)
364
+ Thread.current[EXECUTE_CALLBACKS]
365
+ else
366
+ true
367
+ end
368
+ end
369
+
370
+ # Indicates whether document callbacks should be invoked by default for
371
+ # the current thread. Individual documents may further override the
372
+ # callback behavior, but this will be used for the default behavior.
373
+ #
374
+ # @param flag [ true | false ] Whether or not document callbacks should be
375
+ # executed by default.
376
+ def execute_callbacks=(flag)
377
+ Thread.current[EXECUTE_CALLBACKS] = flag
378
+ end
349
379
  end
350
380
  end
@@ -237,7 +237,7 @@ module Mongoid
237
237
  remove_ivar(name)
238
238
  else
239
239
  relation = send(name)
240
- relation.send(:delete_one, child)
240
+ relation._remove(child)
241
241
  end
242
242
  end
243
243
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.0.3"
4
+ VERSION = "8.0.5"
5
5
  end