mongoid 8.0.2 → 8.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +17 -15
  4. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +4 -0
  5. data/lib/mongoid/association/referenced/has_many/proxy.rb +4 -0
  6. data/lib/mongoid/cacheable.rb +2 -2
  7. data/lib/mongoid/criteria/queryable/extensions/array.rb +1 -1
  8. data/lib/mongoid/criteria/queryable/extensions/hash.rb +1 -1
  9. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +0 -8
  10. data/lib/mongoid/criteria/queryable/extensions/string.rb +1 -11
  11. data/lib/mongoid/criteria/queryable/extensions/symbol.rb +0 -10
  12. data/lib/mongoid/criteria/translator.rb +45 -0
  13. data/lib/mongoid/criteria.rb +1 -0
  14. data/lib/mongoid/document.rb +50 -13
  15. data/lib/mongoid/extensions/big_decimal.rb +4 -0
  16. data/lib/mongoid/extensions/float.rb +6 -2
  17. data/lib/mongoid/extensions/integer.rb +6 -2
  18. data/lib/mongoid/factory.rb +21 -8
  19. data/lib/mongoid/fields/localized.rb +7 -2
  20. data/lib/mongoid/matcher.rb +21 -6
  21. data/lib/mongoid/persistence_context.rb +41 -5
  22. data/lib/mongoid/scopable.rb +9 -7
  23. data/lib/mongoid/shardable.rb +35 -11
  24. data/lib/mongoid/threaded.rb +33 -3
  25. data/lib/mongoid/traversable.rb +1 -1
  26. data/lib/mongoid/version.rb +1 -1
  27. data/spec/integration/i18n_fallbacks_spec.rb +1 -17
  28. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +37 -32
  29. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +143 -197
  30. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +102 -114
  31. data/spec/mongoid/attributes_spec.rb +2 -2
  32. data/spec/mongoid/cacheable_spec.rb +3 -3
  33. data/spec/mongoid/clients_spec.rb +25 -0
  34. data/spec/mongoid/contextual/memory_spec.rb +4 -5
  35. data/spec/mongoid/contextual/mongo_spec.rb +2 -4
  36. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +0 -59
  37. data/spec/mongoid/criteria/queryable/extensions/symbol_spec.rb +0 -59
  38. data/spec/mongoid/criteria/queryable/optional_spec.rb +15 -0
  39. data/spec/mongoid/criteria/translator_spec.rb +132 -0
  40. data/spec/mongoid/criteria_projection_spec.rb +0 -1
  41. data/spec/mongoid/criteria_spec.rb +1 -1
  42. data/spec/mongoid/extensions/big_decimal_spec.rb +15 -0
  43. data/spec/mongoid/extensions/float_spec.rb +10 -3
  44. data/spec/mongoid/extensions/integer_spec.rb +10 -3
  45. data/spec/mongoid/fields/localized_spec.rb +37 -12
  46. data/spec/mongoid/shardable_models.rb +14 -0
  47. data/spec/mongoid/shardable_spec.rb +153 -61
  48. data/spec/mongoid/validatable/uniqueness_spec.rb +0 -1
  49. data/spec/support/macros.rb +16 -0
  50. data.tar.gz.sig +0 -0
  51. metadata +651 -643
  52. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c2c1f57188d73b1d7f6d965b01a05cd97ddab316855221e6b23f70858e820af
4
- data.tar.gz: 0ad8309b683b8031796d3e3d99c36840481b7b2cc8aa773e32c4d1b050380671
3
+ metadata.gz: 9ab3c68fb6c373b704eb2d459a98d7a4cb99d73b87e720eabb553fcd44928d17
4
+ data.tar.gz: d0cd7fe683015dd257c48a7d0c301dd9c6972ea51be4b8199ae88b81dc45c8d6
5
5
  SHA512:
6
- metadata.gz: 00ca56917eafe55f9b4ef394569ac8a64023c67906fdd65522d74a95046a7cacab42ca11baea2917e80f803cadc9c7d484e170227fcdf48a14a8656e96639fdb
7
- data.tar.gz: 159b5215f615ca52777a81127e8312767d4e66adbb77e047d01769ed90281ef4959de4bda42e5ce2eadd9803e4a3d11f5d3d50c8da467241f3dd16bd90f69226
6
+ metadata.gz: 247213c01b169b66f1441ccf892590c068b116e4a4998e15cb50c0cdf3b11648bc543a0c935165a7674a88e783734df36dfeed481079d759fcbf473c068686de
7
+ data.tar.gz: fdf737044c01f8089e8bae7dbc14cce78e48e998ea810cd8e9a334f1bdc8c2b8e1ba6c885c8032c3b5da8b70d24b35b99dd90c260c87abea31a93edef15429cc
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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
  #
@@ -15,7 +15,7 @@ module Mongoid
15
15
  # plural model name.
16
16
  #
17
17
  # If new_record? - will append /new
18
- # If not - will append /id-updated_at.to_s(cache_timestamp_format)
18
+ # If not - will append /id-updated_at.to_formatted_s(cache_timestamp_format)
19
19
  # Without updated_at - will append /id
20
20
  #
21
21
  # This is usually called inside a cache() block
@@ -26,7 +26,7 @@ module Mongoid
26
26
  # @return [ String ] the string with or without updated_at
27
27
  def cache_key
28
28
  return "#{model_key}/new" if new_record?
29
- return "#{model_key}/#{_id}-#{updated_at.utc.to_s(cache_timestamp_format)}" if do_or_do_not(:updated_at)
29
+ return "#{model_key}/#{_id}-#{updated_at.utc.to_formatted_s(cache_timestamp_format)}" if do_or_do_not(:updated_at)
30
30
  "#{model_key}/#{_id}"
31
31
  end
32
32
  end
@@ -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.
@@ -72,10 +72,14 @@ module Mongoid
72
72
  BSON::Decimal128.new(object)
73
73
  elsif object.numeric?
74
74
  BSON::Decimal128.new(object.to_s)
75
+ elsif !object.is_a?(String)
76
+ object.try(:to_d)
75
77
  end
76
78
  else
77
79
  if object.is_a?(BSON::Decimal128) || object.numeric?
78
80
  object.to_s
81
+ elsif !object.is_a?(String)
82
+ object.try(:to_d)
79
83
  end
80
84
  end
81
85
  end
@@ -37,8 +37,12 @@ module Mongoid
37
37
  # @return [ Float | nil ] The object mongoized or nil.
38
38
  def mongoize(object)
39
39
  return if object.blank?
40
- if object.numeric?
41
- object.to_f
40
+ if object.is_a?(String)
41
+ if object.numeric?
42
+ object.to_f
43
+ end
44
+ else
45
+ object.try(:to_f)
42
46
  end
43
47
  end
44
48
  alias :demongoize :mongoize
@@ -45,8 +45,12 @@ module Mongoid
45
45
  # @return [ Integer | nil ] The object mongoized or nil.
46
46
  def mongoize(object)
47
47
  return if object.blank?
48
- if object.numeric?
49
- object.to_i
48
+ if object.is_a?(String)
49
+ if object.numeric?
50
+ object.to_i
51
+ end
52
+ else
53
+ object.try(:to_i)
50
54
  end
51
55
  end
52
56
  alias :demongoize :mongoize
@@ -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
@@ -14,7 +14,9 @@ module Mongoid
14
14
  #
15
15
  # @return [ Object ] The value for the current locale.
16
16
  def demongoize(object)
17
- if object
17
+ return if object.nil?
18
+ case object
19
+ when Hash
18
20
  type.demongoize(lookup(object))
19
21
  end
20
22
  end
@@ -76,7 +78,10 @@ module Mongoid
76
78
  end
77
79
  return value unless value.nil?
78
80
  if fallbacks? && ::I18n.respond_to?(:fallbacks)
79
- object[::I18n.fallbacks[locale].map(&:to_s).find{ |loc| object.has_key?(loc) }]
81
+ fallback_key = ::I18n.fallbacks[locale].find do |loc|
82
+ object.key?(loc.to_s) || object.key?(loc)
83
+ end
84
+ object[fallback_key.to_s] || object[fallback_key]
80
85
  end
81
86
  end
82
87
  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
 
@@ -189,8 +189,7 @@ module Mongoid
189
189
  #
190
190
  # @return [ Mongoid::PersistenceContext ] The persistence context for the object.
191
191
  def set(object, options_or_context)
192
- key = "[mongoid][#{object.object_id}]:context"
193
- existing_context = Thread.current[key]
192
+ existing_context = get_context(object)
194
193
  existing_options = if existing_context
195
194
  existing_context.options
196
195
  else
@@ -201,7 +200,7 @@ module Mongoid
201
200
  end
202
201
  new_options = existing_options.merge(options_or_context)
203
202
  context = PersistenceContext.new(object, new_options)
204
- Thread.current[key] = context
203
+ store_context(object, context)
205
204
  end
206
205
 
207
206
  # Get the persistence context for a particular class or model instance.
@@ -213,7 +212,7 @@ module Mongoid
213
212
  #
214
213
  # @return [ Mongoid::PersistenceContext ] The persistence context for the object.
215
214
  def get(object)
216
- Thread.current["[mongoid][#{object.object_id}]:context"]
215
+ get_context(object)
217
216
  end
218
217
 
219
218
  # Clear the persistence context for a particular class or model instance.
@@ -232,7 +231,44 @@ module Mongoid
232
231
  end
233
232
  end
234
233
  ensure
235
- Thread.current["[mongoid][#{object.object_id}]:context"] = original_context
234
+ store_context(object, original_context)
235
+ end
236
+
237
+ private
238
+
239
+ # Key to store persistence contexts in the thread local storage.
240
+ #
241
+ # @api private
242
+ PERSISTENCE_CONTEXT_KEY = :"[mongoid]:persistence_context"
243
+
244
+ # Get the persistence context for a given object from the thread local
245
+ # storage.
246
+ #
247
+ # @param [ Object ] object Object to get the persistance context for.
248
+ #
249
+ # @return [ Mongoid::PersistenceContext | nil ] The persistence context
250
+ # for the object if previously stored, otherwise nil.
251
+ #
252
+ # @api private
253
+ def get_context(object)
254
+ Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
255
+ Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id]
256
+ end
257
+
258
+ # Store persistence context for a given object in the thread local
259
+ # storage.
260
+ #
261
+ # @param [ Object ] object Object to store the persistance context for.
262
+ # @param [ Mongoid::PersistenceContext ] context Context to store
263
+ #
264
+ # @api private
265
+ def store_context(object, context)
266
+ if context.nil?
267
+ Thread.current[PERSISTENCE_CONTEXT_KEY]&.delete(object.object_id)
268
+ else
269
+ Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
270
+ Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id] = context
271
+ end
236
272
  end
237
273
  end
238
274
  end
@@ -168,7 +168,9 @@ module Mongoid
168
168
  # Band.where(name: "Depeche Mode")
169
169
  # end
170
170
  #
171
- # @note This will force the default scope to be removed.
171
+ # @note This will force the default scope to be removed, but will not
172
+ # remove scopes declared with ``.with_scope``. This will be changed
173
+ # in Mongoid 9.
172
174
  #
173
175
  # @return [ Criteria | Object ] The unscoped criteria or result of the
174
176
  # block.
@@ -249,12 +251,12 @@ module Mongoid
249
251
  if Mongoid.scope_overwrite_exception
250
252
  raise Errors::ScopeOverwrite.new(self.name, name)
251
253
  else
252
- if Mongoid.logger
253
- Mongoid.logger.warn(
254
- "Creating scope :#{name}. " +
255
- "Overwriting existing method #{self.name}.#{name}."
256
- )
257
- end
254
+ Mongoid.logger.warn(
255
+ "Creating scope :#{name} which conflicts with #{self.name}.#{name}. " +
256
+ "Calls to `Mongoid::Criteria##{name}` will delegate to " +
257
+ "`Mongoid::Criteria##{name}` for criteria with klass #{self.name} " +
258
+ "and will ignore the declared scope."
259
+ )
258
260
  end
259
261
  end
260
262
  end