mongoid 7.0.2 → 7.0.3

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/mongoid/association/referenced/eager.rb +4 -1
  4. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +3 -1
  5. data/lib/mongoid/association/referenced/has_many/proxy.rb +2 -2
  6. data/lib/mongoid/association/relatable.rb +90 -10
  7. data/lib/mongoid/clients/options.rb +6 -4
  8. data/lib/mongoid/copyable.rb +2 -2
  9. data/lib/mongoid/criteria/options.rb +2 -2
  10. data/lib/mongoid/criteria/queryable/selectable.rb +33 -6
  11. data/lib/mongoid/document.rb +11 -4
  12. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  13. data/lib/mongoid/extensions/regexp.rb +1 -0
  14. data/lib/mongoid/matchable.rb +3 -1
  15. data/lib/mongoid/matchable/eq.rb +22 -0
  16. data/lib/mongoid/matchable/ne.rb +1 -1
  17. data/lib/mongoid/persistence_context.rb +20 -5
  18. data/lib/mongoid/query_cache.rb +8 -4
  19. data/lib/mongoid/railtie.rb +17 -0
  20. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  21. data/lib/mongoid/scopable.rb +3 -3
  22. data/lib/mongoid/threaded.rb +36 -0
  23. data/lib/mongoid/version.rb +1 -1
  24. data/spec/app/models/minim.rb +7 -0
  25. data/spec/app/models/store_as_dup_test3.rb +7 -0
  26. data/spec/app/models/store_as_dup_test4.rb +7 -0
  27. data/spec/config/mongoid.yml +12 -3
  28. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  29. data/spec/lite_spec_helper.rb +56 -0
  30. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +24 -5
  31. data/spec/mongoid/association/referenced/belongs_to_spec.rb +46 -3
  32. data/spec/mongoid/association/referenced/has_and_belongs_to_many/eager_spec.rb +21 -2
  33. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_persistence_spec.rb +56 -0
  34. data/spec/mongoid/association/referenced/has_and_belongs_to_many_models.rb +26 -0
  35. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +3 -3
  36. data/spec/mongoid/association/referenced/has_many/proxy_query_spec.rb +23 -0
  37. data/spec/mongoid/association/referenced/has_many_models.rb +37 -0
  38. data/spec/mongoid/association/referenced/has_many_spec.rb +3 -3
  39. data/spec/mongoid/association/referenced/has_one_models.rb +48 -0
  40. data/spec/mongoid/association/referenced/has_one_spec.rb +51 -4
  41. data/spec/mongoid/clients/factory_spec.rb +24 -18
  42. data/spec/mongoid/clients/options_spec.rb +40 -37
  43. data/spec/mongoid/clients_spec.rb +68 -8
  44. data/spec/mongoid/config_spec.rb +3 -1
  45. data/spec/mongoid/contextual/mongo_spec.rb +5 -2
  46. data/spec/mongoid/copyable_spec.rb +40 -6
  47. data/spec/mongoid/copyable_spec_models.rb +17 -0
  48. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  49. data/spec/mongoid/criteria/queryable/selectable_spec.rb +42 -3
  50. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  51. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  52. data/spec/mongoid/criteria_spec.rb +18 -3
  53. data/spec/mongoid/document_spec.rb +81 -2
  54. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  55. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  56. data/spec/mongoid/fields_spec.rb +1 -1
  57. data/spec/mongoid/findable_spec.rb +1 -1
  58. data/spec/mongoid/matchable/eq_spec.rb +48 -0
  59. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  60. data/spec/mongoid/persistence_context_spec.rb +1 -1
  61. data/spec/mongoid/query_cache_spec.rb +59 -6
  62. data/spec/mongoid/scopable_spec.rb +13 -0
  63. data/spec/mongoid/threaded_spec.rb +68 -0
  64. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  65. data/spec/spec_helper.rb +35 -25
  66. data/spec/support/constraints.rb +101 -0
  67. data/spec/support/macros.rb +20 -0
  68. data/spec/support/spec_config.rb +39 -0
  69. metadata +471 -460
  70. checksums.yaml.gz.sig +0 -0
  71. data.tar.gz.sig +0 -2
  72. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e3c2f9aa40e2f20c7620a2ae7684da4ebfc1699e32357dd4aa663d93b7ade33
4
- data.tar.gz: 9f0d2145026c3db222638b7d00d842a690294cc6101a582a14a75607c44474a1
3
+ metadata.gz: 6f9840f2a5bdcfd628358c56f70818857080118680059a6ca299c6de3b4554c2
4
+ data.tar.gz: f8700656e769546f2c8d2555013aa601d10552e9e48faf37f24c66fded700b1d
5
5
  SHA512:
6
- metadata.gz: 0f9b11ae060df18208b48b911ee39c2954a7855ac7ffc19b073aa21a707e51805bb465b73b7634abb425d983b3d57fbbab044a202853e532c0fc8331141b9734
7
- data.tar.gz: 24d37b3c3b77d110d45ec6df6209e8cf4a7821fb787c4d369cd87f01bcf66db0292e101928f8ab2a102f5020c6bb4d7b4b19b13fc2c7765096f5f409251825ff
6
+ metadata.gz: a05fb92510939cf2ccff55855219033db8a9a45399865f725e695af95a9501f72a0db72422766dd54fc9946854e0065b9beb870bfeb3a02b8ac9e605962419e0
7
+ data.tar.gz: 267ff19b100ca5ef83f383ddd8d4ece8ce0e122a16cbc6c1605bc872e391ad775bd6336beb53af733ba8d7b47d9ec01d9842f5502c7f0c135cdc2fa258b80b47
data/README.md CHANGED
@@ -21,7 +21,7 @@ Project Tracking
21
21
  Compatibility
22
22
  -------------
23
23
 
24
- Mongoid is tested against MRI 2.2, 2.3 and JRuby (9.1).
24
+ Mongoid is tested against MRI 2.2-2.5 and JRuby 9.1-9.2.
25
25
 
26
26
  Documentation
27
27
  -------------
@@ -64,7 +64,10 @@ module Mongoid
64
64
  #
65
65
  # @since 4.0.0
66
66
  def each_loaded_document
67
- criteria = @association.klass.any_in(key => keys_from_docs)
67
+ doc_keys = keys_from_docs
68
+ return @association.klass.none if doc_keys.all?(&:nil?)
69
+
70
+ criteria = @association.klass.any_in(key => doc_keys)
68
71
  criteria.inclusions = criteria.inclusions - [@association]
69
72
  criteria.each do |doc|
70
73
  yield doc
@@ -135,7 +135,9 @@ module Mongoid
135
135
  end
136
136
  unless _association.forced_nil_inverse?
137
137
  if replacement
138
- objects_to_clear = _base.send(foreign_key) - replacement.collect(&:id)
138
+ objects_to_clear = _base.send(foreign_key) - replacement.collect do |object|
139
+ object.send(_association.primary_key)
140
+ end
139
141
  criteria(objects_to_clear).pull(inverse_foreign_key => _base._id)
140
142
  else
141
143
  criteria.pull(inverse_foreign_key => _base._id)
@@ -284,7 +284,7 @@ module Mongoid
284
284
  new_ids = new_docs.map { |doc| doc._id }
285
285
  remove_not_in(new_ids)
286
286
  new_docs.each do |doc|
287
- docs.push(doc) if doc.send(foreign_key) != _base._id
287
+ docs.push(doc) if doc.send(foreign_key) != _base.send(_association.primary_key)
288
288
  end
289
289
  concat(docs)
290
290
  else
@@ -303,7 +303,7 @@ module Mongoid
303
303
  #
304
304
  # @since 2.4.0
305
305
  def unscoped
306
- klass.unscoped.where(foreign_key => _base._id)
306
+ klass.unscoped.where(foreign_key => _base.send(_association.primary_key))
307
307
  end
308
308
 
309
309
  private
@@ -147,23 +147,64 @@ module Mongoid
147
147
 
148
148
  # The class name of the relation object(s).
149
149
  #
150
- # @return [ String ] The relation objects' class name.
150
+ # The class name may be fully qualified or may be specified relative
151
+ # to the class on which the association is defined (this class is
152
+ # accessible as inverse_class). If :class_name option is given in the
153
+ # association, the exact value of that option is returned here for
154
+ # backwards compatibility reasons. If :class_name option is not given,
155
+ # the name of the class computed by Mongoid to be the association target
156
+ # is returned, and it will be fully qualified.
157
+ #
158
+ # The class name returned by this method may not correspond to a defined
159
+ # class. The return value of the method is the class name that Mongoid
160
+ # would reference, relative to the host document class, when it needs to
161
+ # perform operations on the association target.
162
+ #
163
+ # @note The return value of this method should not be used to determine
164
+ # whether two associations have the same target class, because the
165
+ # return value is not always a fully qualified class name. To compare
166
+ # classes, retrieve the class instance of the association target using
167
+ # the +relation_class+ method.
168
+ #
169
+ # @return [ String ] The association objects' class name.
151
170
  #
152
171
  # @since 7.0
153
172
  def relation_class_name
154
- @class_name ||= @options[:class_name] || ActiveSupport::Inflector.classify(name_with_module)
173
+ @class_name ||= @options[:class_name] || begin
174
+ cls_name = ActiveSupport::Inflector.classify(name)
175
+ begin
176
+ cls_name = resolve_name(inverse_class, cls_name).name
177
+ rescue NameError
178
+ # ignore
179
+ end
180
+ cls_name
181
+ end
155
182
  end
156
183
  alias :class_name :relation_class_name
157
184
 
158
185
  # The class of the relation object(s).
159
186
  #
160
- # @return [ String ] The relation objects' class.
187
+ # This method returns the class instance corresponding to
188
+ # +relation_class_name+, resolved relative to the host document class.
189
+ #
190
+ # If the class does not exist, this method raises NameError. This can
191
+ # happen because the target class has not yet been defined. Note that
192
+ # polymorphic associations generally do not have a well defined target
193
+ # class because the target class can change from one object to another,
194
+ # and calling this method on a polymorphic association will generally
195
+ # fail with a NameError or produce misleading results (if a class does
196
+ # happen to be defined with the same name as the association name).
197
+ #
198
+ # @return [ String ] The association objects' class.
161
199
  #
162
200
  # @since 7.0
163
- def klass
164
- @klass ||= relation_class_name.constantize
201
+ def relation_class
202
+ @klass ||= begin
203
+ cls_name = @options[:class_name] || ActiveSupport::Inflector.classify(name)
204
+ resolve_name(inverse_class, cls_name)
205
+ end
165
206
  end
166
- alias :relation_class :klass
207
+ alias :klass :relation_class
167
208
 
168
209
  # The class name of the object owning this relation.
169
210
  #
@@ -325,10 +366,6 @@ module Mongoid
325
366
 
326
367
  private
327
368
 
328
- def name_with_module
329
- @module_path + name.to_s.capitalize
330
- end
331
-
332
369
  # Gets the model classes with inverse associations of this model. This is used to determine
333
370
  # the classes on the other end of polymorphic relations with models.
334
371
  def inverse_association_classes
@@ -424,6 +461,49 @@ module Mongoid
424
461
  def default_inverse
425
462
  @default_inverse ||= klass.relations[inverse_klass.name.underscore]
426
463
  end
464
+
465
+ # Returns an array of classes/modules forming the namespace hierarchy
466
+ # where symbols referenced in the provided class/module would be looked
467
+ # up by Ruby. For example, if mod is Foo::Bar, this method would return
468
+ # [Foo::Bar, Foo, Object].
469
+ def namespace_hierarchy(mod)
470
+ parent = Object
471
+ hier = [parent]
472
+ mod.name.split('::').each do |part|
473
+ parent = parent.const_get(part)
474
+ hier << parent
475
+ end
476
+ hier.reverse
477
+ end
478
+
479
+ # Resolves the given class/module name in the context of the specified
480
+ # module, as Ruby would when a constant is referenced in the source.
481
+ def resolve_name(mod, name)
482
+ cls = exc = nil
483
+ parts = name.to_s.split('::')
484
+ namespace_hierarchy(mod).each do |ns|
485
+ begin
486
+ parts.each do |part|
487
+ # Simple const_get sometimes pulls names out of weird scopes,
488
+ # perhaps confusing the receiver (ns in this case) with the
489
+ # local scope. Walk the class hierarchy ourselves one node
490
+ # at a time by specifying false as the second argument.
491
+ ns = ns.const_get(part, false)
492
+ end
493
+ cls = ns
494
+ break
495
+ rescue NameError => e
496
+ if exc.nil?
497
+ exc = e
498
+ end
499
+ end
500
+ end
501
+ if cls.nil?
502
+ # Raise the first exception, this is from the most specific namespace
503
+ raise exc
504
+ end
505
+ cls
506
+ end
427
507
  end
428
508
  end
429
509
  end
@@ -20,11 +20,12 @@ module Mongoid
20
20
  #
21
21
  # @since 6.0.0
22
22
  def with(options_or_context, &block)
23
+ original_context = PersistenceContext.get(self)
23
24
  original_cluster = persistence_context.cluster
24
25
  set_persistence_context(options_or_context)
25
26
  yield self
26
27
  ensure
27
- clear_persistence_context(original_cluster)
28
+ clear_persistence_context(original_cluster, original_context)
28
29
  end
29
30
 
30
31
  def collection(parent = nil)
@@ -51,8 +52,8 @@ module Mongoid
51
52
  PersistenceContext.set(self, options_or_context)
52
53
  end
53
54
 
54
- def clear_persistence_context(original_cluster = nil)
55
- PersistenceContext.clear(self, original_cluster)
55
+ def clear_persistence_context(original_cluster = nil, context = nil)
56
+ PersistenceContext.clear(self, original_cluster, context)
56
57
  end
57
58
 
58
59
  module ClassMethods
@@ -92,11 +93,12 @@ module Mongoid
92
93
  #
93
94
  # @since 6.0.0
94
95
  def with(options, &block)
96
+ original_context = PersistenceContext.get(self)
95
97
  original_cluster = persistence_context.cluster
96
98
  PersistenceContext.set(self, options)
97
99
  yield self
98
100
  ensure
99
- PersistenceContext.clear(self, original_cluster)
101
+ PersistenceContext.clear(self, original_cluster, original_context)
100
102
  end
101
103
 
102
104
  def persistence_context
@@ -73,8 +73,8 @@ module Mongoid
73
73
  next unless attrs.present? && attrs[association.key].present?
74
74
 
75
75
  if association.is_a?(Association::Embedded::EmbedsMany)
76
- attrs[association.name.to_s].each do |attr|
77
- embedded_klass = attr.fetch('_type', association.class_name).constantize
76
+ attrs[association.key].each do |attr|
77
+ embedded_klass = attr.fetch('_type', association.relation_class_name).constantize
78
78
  process_localized_attributes(embedded_klass, attr)
79
79
  end
80
80
  else
@@ -17,8 +17,8 @@ module Mongoid
17
17
  PersistenceContext.set(klass, options)
18
18
  end
19
19
 
20
- def clear_persistence_context(original_cluster)
21
- PersistenceContext.clear(klass, original_cluster)
20
+ def clear_persistence_context(original_cluster, original_context)
21
+ PersistenceContext.clear(klass, original_cluster, original_context)
22
22
  end
23
23
  end
24
24
  end
@@ -134,13 +134,21 @@ module Mongoid
134
134
  ::Boolean.evolve(value)
135
135
  end
136
136
 
137
- # Add a $geoIntersects or $geoWithin selection. Symbol operators must be used as shown in
138
- # the examples to expand the criteria.
137
+ # Add a $geoIntersects or $geoWithin selection. Symbol operators must
138
+ # be used as shown in the examples to expand the criteria.
139
139
  #
140
140
  # @note The only valid geometry shapes for a $geoIntersects are:
141
141
  # :intersects_line, :intersects_point, and :intersects_polygon.
142
142
  #
143
- # @note The only valid geometry shape for a $geoWithin is :within_polygon
143
+ # @note The only valid options for a $geoWithin query are the geometry
144
+ # shape :within_polygon and the operator :within_box.
145
+ #
146
+ # @note The :within_box operator for the $geoWithin query expects the
147
+ # lower left (south west) coordinate pair as the first argument and
148
+ # the upper right (north east) as the second argument.
149
+ # Important: When latitude and longitude are passed, longitude is
150
+ # expected as the first element of the coordinate pair.
151
+ # Source: https://docs.mongodb.com/manual/reference/operator/query/box/
144
152
  #
145
153
  # @example Add a geo intersect criterion for a line.
146
154
  # query.geo_spacial(:location.intersects_line => [[ 1, 10 ], [ 2, 10 ]])
@@ -154,6 +162,9 @@ module Mongoid
154
162
  # @example Add a geo within criterion for a polygon.
155
163
  # query.geo_spacial(:location.within_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
156
164
  #
165
+ # @example Add a geo within criterion for a box.
166
+ # query.geo_spacial(:location.within_box => [[ 1, 10 ], [ 2, 10 ])
167
+ #
157
168
  # @param [ Hash ] criterion The criterion.
158
169
  #
159
170
  # @return [ Selectable ] The cloned selectable.
@@ -174,6 +185,7 @@ module Mongoid
174
185
  key :within_polygon, :override, "$geoWithin", "$geometry" do |value|
175
186
  { "type" => POLYGON, "coordinates" => value }
176
187
  end
188
+ key :within_box, :override, "$geoWithin", "$box"
177
189
 
178
190
  # Add the $gt criterion to the selector.
179
191
  #
@@ -501,6 +513,11 @@ module Mongoid
501
513
  # @example Construct a text search selector with options.
502
514
  # selectable.text_search("testing", :$language => "fr")
503
515
  #
516
+ # @note Per https://docs.mongodb.com/manual/reference/operator/query/text/
517
+ # it is not currently possible to supply multiple text search
518
+ # conditions in a query. Mongoid will build such a query but the
519
+ # server will return an error when trying to execute it.
520
+ #
504
521
  # @param [ String, Symbol ] terms A string of terms that MongoDB parses
505
522
  # and uses to query the text index.
506
523
  # @param [ Hash ] opts Text search options. See MongoDB documentation
@@ -512,9 +529,19 @@ module Mongoid
512
529
  def text_search(terms, opts = nil)
513
530
  clone.tap do |query|
514
531
  if terms
515
- criterion = { :$text => { :$search => terms } }
516
- criterion[:$text].merge!(opts) if opts
517
- query.selector = criterion
532
+ criterion = {'$text' => { '$search' => terms }}
533
+ criterion['$text'].merge!(opts) if opts
534
+ if query.selector['$text']
535
+ # Per https://docs.mongodb.com/manual/reference/operator/query/text/
536
+ # multiple $text expressions are not currently supported by
537
+ # MongoDB server, but build the query correctly instead of
538
+ # overwriting previous text search condition with the currently
539
+ # given one.
540
+ Mongoid.logger.warn('Multiple $text expressions per query are not currently supported by the server')
541
+ query.selector = {'$and' => [query.selector]}.merge(criterion)
542
+ else
543
+ query.selector = query.selector.merge(criterion)
544
+ end
518
545
  end
519
546
  end
520
547
  end
@@ -180,6 +180,13 @@ module Mongoid
180
180
 
181
181
  # Calls #as_json on the document with additional, Mongoid-specific options.
182
182
  #
183
+ # @note Rails 6 changes return value of as_json for non-primitive types
184
+ # such as BSON::ObjectId. In Rails <= 5, as_json returned these as
185
+ # instances of the class. In Rails 6, these are returned serialized to
186
+ # primitive types (e.g. {"$oid"=>"5bcfc40bde340b37feda98e9"}).
187
+ # See https://github.com/rails/rails/commit/2e5cb980a448e7f4ab00df6e9ad4c1cc456616aa
188
+ # for more information.
189
+ #
183
190
  # @example Get the document as json.
184
191
  # document.as_json(compact: true)
185
192
  #
@@ -192,11 +199,11 @@ module Mongoid
192
199
  #
193
200
  # @since 5.1.0
194
201
  def as_json(options = nil)
195
- if options && (options[:compact] == true)
196
- super(options).reject! { |k,v| v.nil? }
197
- else
198
- super(options)
202
+ rv = super
203
+ if options && options[:compact]
204
+ rv = rv.compact
199
205
  end
206
+ rv
200
207
  end
201
208
 
202
209
  # Returns an instance of the specified class with the attributes,
@@ -53,7 +53,7 @@ module Mongoid
53
53
  #
54
54
  # @since 3.0.0
55
55
  def demongoize(object)
56
- object && object.numeric? ? ::BigDecimal.new(object.to_s) : nil
56
+ object && object.numeric? ? BigDecimal(object.to_s) : nil
57
57
  end
58
58
 
59
59
  # Mongoize an object of any type to how it's stored in the db as a String.
@@ -17,6 +17,7 @@ module Mongoid
17
17
  #
18
18
  # @since 3.0.0
19
19
  def mongoize(object)
20
+ return nil if object.nil?
20
21
  ::Regexp.new(object)
21
22
  end
22
23
  end
@@ -2,6 +2,7 @@
2
2
  require "mongoid/matchable/default"
3
3
  require "mongoid/matchable/all"
4
4
  require "mongoid/matchable/and"
5
+ require "mongoid/matchable/eq"
5
6
  require "mongoid/matchable/exists"
6
7
  require "mongoid/matchable/gt"
7
8
  require "mongoid/matchable/gte"
@@ -32,6 +33,7 @@ module Mongoid
32
33
  "$all" => All,
33
34
  "$elemMatch" => ElemMatch,
34
35
  "$and" => And,
36
+ "$eq" => Eq,
35
37
  "$exists" => Exists,
36
38
  "$gt" => Gt,
37
39
  "$gte" => Gte,
@@ -42,7 +44,7 @@ module Mongoid
42
44
  "$nin" => Nin,
43
45
  "$or" => Or,
44
46
  "$nor" => Nor,
45
- "$size" => Size
47
+ "$size" => Size,
46
48
  }.with_indifferent_access.freeze
47
49
 
48
50
  # Determines if this document has the attributes to match the supplied
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+ module Mongoid
4
+ module Matchable
5
+
6
+ # Performs equivalency checks.
7
+ class Eq < Default
8
+
9
+ # Return true if the attribute and first value are equal.
10
+ #
11
+ # @example Do the values match?
12
+ # matcher._matches?({ :key => 10 })
13
+ #
14
+ # @param [ Hash ] value The values to check.
15
+ #
16
+ # @return [ true, false ] True if matches, false if not.
17
+ def _matches?(value)
18
+ super(value.values.first)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -12,7 +12,7 @@ module Mongoid
12
12
  #
13
13
  # @param [ Hash ] value The values to check.
14
14
  #
15
- # @return [ true, false ] If a value exists.
15
+ # @return [ true, false ] True if matches, false if not.
16
16
  def _matches?(value)
17
17
  !super(value.values.first)
18
18
  end
@@ -169,6 +169,9 @@ module Mongoid
169
169
 
170
170
  # Set the persistence context for a particular class or model instance.
171
171
  #
172
+ # If there already is a persistence context set, options in the existing
173
+ # context are combined with options given to the set call.
174
+ #
172
175
  # @example Set the persistence context for a class or model instance.
173
176
  # PersistenceContext.set(model)
174
177
  #
@@ -180,9 +183,19 @@ module Mongoid
180
183
  #
181
184
  # @since 6.0.0
182
185
  def set(object, options_or_context)
183
- context = PersistenceContext.new(object, options_or_context.is_a?(PersistenceContext) ?
184
- options_or_context.options : options_or_context)
185
- Thread.current["[mongoid][#{object.object_id}]:context"] = context
186
+ key = "[mongoid][#{object.object_id}]:context"
187
+ existing_context = Thread.current[key]
188
+ existing_options = if existing_context
189
+ existing_context.options
190
+ else
191
+ {}
192
+ end
193
+ if options_or_context.is_a?(PersistenceContext)
194
+ options_or_context = options_or_context.options
195
+ end
196
+ new_options = existing_options.merge(options_or_context)
197
+ context = PersistenceContext.new(object, new_options)
198
+ Thread.current[key] = context
186
199
  end
187
200
 
188
201
  # Get the persistence context for a particular class or model instance.
@@ -206,14 +219,16 @@ module Mongoid
206
219
  #
207
220
  # @param [ Class, Object ] object The class or model instance.
208
221
  # @param [ Mongo::Cluster ] cluster The original cluster before this context was used.
222
+ # @param [ Mongoid::PersistenceContext ] original_context The original persistence
223
+ # context that was set before this context was used.
209
224
  #
210
225
  # @since 6.0.0
211
- def clear(object, cluster = nil)
226
+ def clear(object, cluster = nil, original_context = nil)
212
227
  if context = get(object)
213
228
  context.client.close unless (context.cluster.equal?(cluster) || cluster.nil?)
214
229
  end
215
230
  ensure
216
- Thread.current["[mongoid][#{object.object_id}]:context"] = nil
231
+ Thread.current["[mongoid][#{object.object_id}]:context"] = original_context
217
232
  end
218
233
  end
219
234
  end