mongoid 8.0.2 → 8.0.4

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 (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