mongoid 8.0.5 → 8.0.7

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/lib/mongoid/association/macros.rb +6 -0
  4. data/lib/mongoid/attributes/processing.rb +29 -5
  5. data/lib/mongoid/config/options.rb +3 -0
  6. data/lib/mongoid/config.rb +30 -0
  7. data/lib/mongoid/contextual/mongo.rb +24 -1
  8. data/lib/mongoid/criteria/queryable/selector.rb +1 -1
  9. data/lib/mongoid/criteria/queryable/storable.rb +1 -1
  10. data/lib/mongoid/deprecable.rb +3 -2
  11. data/lib/mongoid/deprecation.rb +3 -3
  12. data/lib/mongoid/extensions/hash.rb +24 -2
  13. data/lib/mongoid/fields.rb +45 -18
  14. data/lib/mongoid/interceptable.rb +122 -13
  15. data/lib/mongoid/version.rb +1 -1
  16. data/spec/integration/callbacks_spec.rb +20 -0
  17. data/spec/mongoid/attributes_spec.rb +27 -0
  18. data/spec/mongoid/config_spec.rb +11 -2
  19. data/spec/mongoid/contextual/mongo_spec.rb +89 -14
  20. data/spec/mongoid/copyable_spec.rb +1 -1
  21. data/spec/mongoid/criteria/queryable/selector_spec.rb +75 -2
  22. data/spec/mongoid/criteria/queryable/storable_spec.rb +72 -0
  23. data/spec/mongoid/extensions/hash_spec.rb +3 -3
  24. data/spec/mongoid/fields_spec.rb +43 -0
  25. data/spec/mongoid/interceptable_spec.rb +362 -161
  26. data/spec/shared/lib/mrss/docker_runner.rb +7 -0
  27. data/spec/shared/lib/mrss/event_subscriber.rb +15 -5
  28. data/spec/shared/lib/mrss/lite_constraints.rb +10 -2
  29. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  30. data/spec/shared/lib/mrss/utils.rb +28 -6
  31. data/spec/shared/share/Dockerfile.erb +36 -40
  32. data/spec/shared/shlib/server.sh +32 -8
  33. data/spec/shared/shlib/set_env.sh +4 -4
  34. data/spec/support/models/person.rb +1 -0
  35. data/spec/support/models/purse.rb +9 -0
  36. data.tar.gz.sig +0 -0
  37. metadata +10 -8
  38. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53bba3a9611a89eaf77478beacbf4a8a3c6751503ecf1919407c7af05e134af0
4
- data.tar.gz: ebe8a6705c4c035e99720d1a37e8103948aa1cb61a66ab42b0d426dd7e10c135
3
+ metadata.gz: 5ffe4422a33c5d676a4ae8e28c2d3ea748a738022b5fe0defa69a675fefbff8d
4
+ data.tar.gz: d296900948d3e569beb33f4c63876ac24c45368dd0f3000efebecdcaed2cfbcf
5
5
  SHA512:
6
- metadata.gz: 15c3ecad78d34f2bdc3967042c0175b639d813aa44bcb2e6a5aa4ee5ec6d3adcb13e428accefb0edfa6b7dab59710829194e00cc17cb0f38ff5673957c97e6ce
7
- data.tar.gz: ba5f2a0a8cd2ddc48650d9e7b67c162d4071f254ea9de03070308a411aeda8db2ac40c3a548d9543bed2bad637aa001e2a4ed0fe039cb380a30a69d6b79164fd
6
+ metadata.gz: 3632cdca025ba56792b041d07a560d00f3a561fa21ca9b0f44167fbf54b14fe0cc5ae040adeff7a24ac98795f6a54cfbfcd88ae3057906a44d606573e18ee398
7
+ data.tar.gz: 243a702300881cc8ef82f7937e8ff3ea718b2c23aea8768a6392d47b6f3f841dba062e0133d142855b9f9ccf85f3bfbe0f45b743daed7f82813e02f6b9a1674b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -35,10 +35,15 @@ module Mongoid
35
35
  # @api private
36
36
  class_attribute :aliased_associations
37
37
 
38
+ # @return [ Set<String> ] The set of associations that are configured
39
+ # with :store_as parameter.
40
+ class_attribute :stored_as_associations
41
+
38
42
  self.embedded = false
39
43
  self.embedded_relations = BSON::Document.new
40
44
  self.relations = BSON::Document.new
41
45
  self.aliased_associations = {}
46
+ self.stored_as_associations = Set.new
42
47
  end
43
48
 
44
49
  # This is convenience for libraries still on the old API.
@@ -219,6 +224,7 @@ module Mongoid
219
224
  self.relations = self.relations.merge(name => assoc)
220
225
  if assoc.embedded? && assoc.respond_to?(:store_as) && assoc.store_as != name
221
226
  self.aliased_associations[assoc.store_as] = name
227
+ self.stored_as_associations << assoc.store_as
222
228
  end
223
229
  end
224
230
  end
@@ -43,22 +43,46 @@ module Mongoid
43
43
  # @return [ true | false ] True if pending, false if not.
44
44
  def pending_attribute?(key, value)
45
45
  name = key.to_s
46
-
47
46
  aliased = if aliased_associations.key?(name)
48
47
  aliased_associations[name]
49
48
  else
50
49
  name
51
50
  end
52
-
53
51
  if relations.has_key?(aliased)
54
- pending_relations[name] = value
52
+ set_pending_relation(name, aliased, value)
55
53
  return true
56
54
  end
57
55
  if nested_attributes.has_key?(aliased)
58
- pending_nested[name] = value
56
+ set_pending_nested(name, aliased, value)
59
57
  return true
60
58
  end
61
- return false
59
+ false
60
+ end
61
+
62
+ # Set value of the pending relation.
63
+ #
64
+ # @param [ Symbol ] name The name of the relation.
65
+ # @param [ Symbol ] aliased The aliased name of the relation.
66
+ # @param [ Object ] value The value of the relation.
67
+ def set_pending_relation(name, aliased, value)
68
+ if stored_as_associations.include?(name)
69
+ pending_relations[aliased] = value
70
+ else
71
+ pending_relations[name] = value
72
+ end
73
+ end
74
+
75
+ # Set value of the pending nested attribute.
76
+ #
77
+ # @param [ Symbol ] name The name of the nested attribute.
78
+ # @param [ Symbol ] aliased The aliased name of the nested attribute.
79
+ # @param [ Object ] value The value of the nested attribute.
80
+ def set_pending_nested(name, aliased, value)
81
+ if stored_as_associations.include?(name)
82
+ pending_nested[aliased] = value
83
+ else
84
+ pending_nested[name] = value
85
+ end
62
86
  end
63
87
 
64
88
  # Get all the pending associations that need to be set.
@@ -25,6 +25,8 @@ module Mongoid
25
25
  # @param [ Hash ] options Extras for the option.
26
26
  #
27
27
  # @option options [ Object ] :default The default value.
28
+ # @option options [ Proc | nil ] :on_change The callback to invoke when the
29
+ # setter is invoked.
28
30
  def option(name, options = {})
29
31
  defaults[name] = settings[name] = options[:default]
30
32
 
@@ -38,6 +40,7 @@ module Mongoid
38
40
 
39
41
  define_method("#{name}=") do |value|
40
42
  settings[name] = value
43
+ options[:on_change]&.call(value)
41
44
  end
42
45
 
43
46
  define_method("#{name}?") do
@@ -125,6 +125,36 @@ module Mongoid
125
125
  # always return a Hash.
126
126
  option :legacy_attributes, default: false
127
127
 
128
+ # Allow BSON::Decimal128 to be parsed and returned directly in
129
+ # field values. When BSON 5 is present and the this option is set to false
130
+ # (the default), BSON::Decimal128 values in the database will be returned
131
+ # as BigDecimal.
132
+ #
133
+ # @note this option only has effect when BSON 5+ is present. Otherwise,
134
+ # the setting is ignored.
135
+ option :allow_bson5_decimal128, default: false, on_change: -> (allow) do
136
+ if BSON::VERSION >= '5.0.0'
137
+ if allow
138
+ BSON::Registry.register(BSON::Decimal128::BSON_TYPE, BSON::Decimal128)
139
+ else
140
+ BSON::Registry.register(BSON::Decimal128::BSON_TYPE, BigDecimal)
141
+ end
142
+ end
143
+ end
144
+
145
+ # When this flag is true, callbacks for embedded documents will not be
146
+ # called. This is the default in 8.x, but will be changed to false in 9.0.
147
+ #
148
+ # Setting this flag to true (as it is in 8.x) may lead to stack
149
+ # overflow errors if there are more than cicrca 1000 embedded
150
+ # documents in the root document's dependencies graph.
151
+ #
152
+ # It is strongly recommended to set this flag to false in 8.x, if you
153
+ # are not using around callbacks for embedded documents.
154
+ #
155
+ # See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
156
+ option :around_callbacks_for_embeds, default: true
157
+
128
158
  # Has Mongoid been configured? This is checking that at least a valid
129
159
  # client config exists.
130
160
  #
@@ -56,7 +56,12 @@ module Mongoid
56
56
  # @return [ Integer ] The number of matches.
57
57
  def count(options = {}, &block)
58
58
  return super(&block) if block_given?
59
- view.count_documents(options)
59
+
60
+ if valid_for_count_documents?
61
+ view.count_documents(options)
62
+ else
63
+ view.count(options)
64
+ end
60
65
  end
61
66
 
62
67
  # Get the estimated number of documents matching the query.
@@ -813,6 +818,24 @@ module Mongoid
813
818
  docs = eager_load(docs)
814
819
  limit ? docs : docs.first
815
820
  end
821
+
822
+ # Queries whether the current context is valid for use with
823
+ # the #count_documents? predicate. A context is valid if it
824
+ # does not include a `$where` operator.
825
+ #
826
+ # @return [ true | false ] whether or not the current context
827
+ # excludes a `$where` operator.
828
+ def valid_for_count_documents?(hash = view.filter)
829
+ # Note that `view.filter` is a BSON::Document, and all keys in a
830
+ # BSON::Document are strings; we don't need to worry about symbol
831
+ # representations of `$where`.
832
+ hash.keys.each do |key|
833
+ return false if key == '$where'
834
+ return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
835
+ end
836
+
837
+ true
838
+ end
816
839
  end
817
840
  end
818
841
  end
@@ -20,7 +20,7 @@ module Mongoid
20
20
  other.each_pair do |key, value|
21
21
  if value.is_a?(Hash) && self[key.to_s].is_a?(Hash)
22
22
  value = self[key.to_s].merge(value) do |_key, old_val, new_val|
23
- case _key
23
+ case _key.to_s
24
24
  when '$in'
25
25
  new_val & old_val
26
26
  when '$nin'
@@ -47,7 +47,7 @@ module Mongoid
47
47
  if value.is_a?(Hash) && selector[field].is_a?(Hash) &&
48
48
  value.keys.all? { |key|
49
49
  key_s = key.to_s
50
- key_s.start_with?('$') && !selector[field].key?(key_s)
50
+ key_s.start_with?('$') && !selector[field].keys.map(&:to_s).include?(key_s)
51
51
  }
52
52
  then
53
53
  # Multiple operators can be combined on the same field by
@@ -24,10 +24,11 @@ module Mongoid
24
24
  # #=> Mongoid.logger.warn("meow is deprecated and will be removed from Mongoid 8.0 (eat :catnip instead)")
25
25
  #
26
26
  # @param [ Module ] target_module The parent which contains the method.
27
- # @param [ Symbol | Hash<Symbol, [ Symbol | String ]> ] method_descriptors
27
+ # @param [ [ Symbol | Hash<Symbol, [ Symbol | String ]> ]... ] *method_descriptors
28
28
  # The methods to deprecate, with optional replacement instructions.
29
29
  def deprecate(target_module, *method_descriptors)
30
- Mongoid::Deprecation.deprecate_methods(target_module, *method_descriptors)
30
+ @_deprecator ||= Mongoid::Deprecation.new
31
+ @_deprecator.deprecate_methods(target_module, *method_descriptors)
31
32
  end
32
33
  end
33
34
  end
@@ -15,10 +15,10 @@ module Mongoid
15
15
  #
16
16
  # @return Array<Proc> The deprecation behavior.
17
17
  def behavior
18
- @behavior ||= Array(->(message, callstack, _deprecation_horizon, _gem_name) {
18
+ @behavior ||= Array(->(*args) {
19
19
  logger = Mongoid.logger
20
- logger.warn(message)
21
- logger.debug(callstack.join("\n ")) if debug
20
+ logger.warn(args[0])
21
+ logger.debug(args[1].join("\n ")) if debug
22
22
  })
23
23
  end
24
24
  end
@@ -38,8 +38,12 @@ module Mongoid
38
38
  consolidated = {}
39
39
  each_pair do |key, value|
40
40
  if key =~ /\$/
41
- value.each_pair do |_key, _value|
42
- value[_key] = (key == "$rename") ? _value.to_s : mongoize_for(key, klass, _key, _value)
41
+ value.keys.each do |key2|
42
+ value2 = value[key2]
43
+ real_key = klass.database_field_name(key2)
44
+
45
+ value.delete(key2) if real_key != key2
46
+ value[real_key] = value_for(key, klass, real_key, value2)
43
47
  end
44
48
  consolidated[key] ||= {}
45
49
  consolidated[key].update(value)
@@ -181,6 +185,24 @@ module Mongoid
181
185
 
182
186
  private
183
187
 
188
+ # Get the value for the provided operator, klass, key and value.
189
+ #
190
+ # This is necessary for special cases like $rename, $addToSet and $push.
191
+ #
192
+ # @param [ String ] operator The operator.
193
+ # @param [ Class ] klass The model class.
194
+ # @param [ String | Symbol ] key The field key.
195
+ # @param [ Object ] value The original value.
196
+ #
197
+ # @return [ Object ] Value prepared for the provided operator.
198
+ def value_for(operator, klass, key, value)
199
+ case operator
200
+ when "$rename" then value.to_s
201
+ when "$addToSet", "$push" then value.mongoize
202
+ else mongoize_for(operator, klass, operator, value)
203
+ end
204
+ end
205
+
184
206
  # Mongoize for the klass, key and value.
185
207
  #
186
208
  # @api private
@@ -764,33 +764,60 @@ module Mongoid
764
764
 
765
765
  def field_for(name, options)
766
766
  opts = options.merge(klass: self)
767
- type_mapping = TYPE_MAPPINGS[options[:type]]
768
- opts[:type] = type_mapping || unmapped_type(options)
769
- if !opts[:type].is_a?(Class)
770
- raise Errors::InvalidFieldType.new(self, name, options[:type])
771
- else
772
- if INVALID_BSON_CLASSES.include?(opts[:type])
773
- warn_message = "Using #{opts[:type]} as the field type is not supported. "
774
- if opts[:type] == BSON::Decimal128
775
- warn_message += "In BSON <= 4, the BSON::Decimal128 type will work as expected for both storing and querying, but will return a BigDecimal on query in BSON 5+."
776
- else
777
- warn_message += "Saving values of this type to the database will work as expected, however, querying them will return a value of the native Ruby Integer type."
778
- end
779
- Mongoid.logger.warn(warn_message)
780
- end
781
- end
767
+ opts[:type] = retrieve_and_validate_type(name, options[:type])
782
768
  return Fields::Localized.new(name, opts) if options[:localize]
783
769
  return Fields::ForeignKey.new(name, opts) if options[:identity]
784
770
  Fields::Standard.new(name, opts)
785
771
  end
786
772
 
787
- def unmapped_type(options)
788
- if "Boolean" == options[:type].to_s
773
+ # Get the class for the given type.
774
+ #
775
+ # @param [ Symbol ] name The name of the field.
776
+ # @param [ Symbol | Class ] type The type of the field.
777
+ #
778
+ # @return [ Class ] The type of the field.
779
+ #
780
+ # @raises [ Mongoid::Errors::InvalidFieldType ] if given an invalid field
781
+ # type.
782
+ #
783
+ # @api private
784
+ def retrieve_and_validate_type(name, type)
785
+ result = TYPE_MAPPINGS[type] || unmapped_type(type)
786
+ raise Errors::InvalidFieldType.new(self, name, type) if !result.is_a?(Class)
787
+
788
+ if unsupported_type?(result)
789
+ warn_message = "Using #{result} as the field type is not supported. "
790
+ if result == BSON::Decimal128
791
+ warn_message += 'In BSON <= 4, the BSON::Decimal128 type will work as expected for both storing and querying, but will return a BigDecimal on query in BSON 5+. To use literal BSON::Decimal128 fields with BSON 5, set Mongoid.allow_bson5_decimal128 to true.'
792
+ else
793
+ warn_message += 'Saving values of this type to the database will work as expected, however, querying them will return a value of the native Ruby Integer type.'
794
+ end
795
+ Mongoid.logger.warn(warn_message)
796
+ end
797
+
798
+ result
799
+ end
800
+
801
+ def unmapped_type(type)
802
+ if "Boolean" == type.to_s
789
803
  Mongoid::Boolean
790
804
  else
791
- options[:type] || Object
805
+ type || Object
792
806
  end
793
807
  end
808
+
809
+ # Queries whether or not the given type is permitted as a declared field
810
+ # type.
811
+ #
812
+ # @param [ Class ] type The type to query
813
+ #
814
+ # @return [ true | false ] whether or not the type is supported
815
+ #
816
+ # @api private
817
+ def unsupported_type?(type)
818
+ return !Mongoid::Config.allow_bson5_decimal128? if type == BSON::Decimal128
819
+ INVALID_BSON_CLASSES.include?(type)
820
+ end
794
821
  end
795
822
  end
796
823
  end
@@ -79,7 +79,7 @@ module Mongoid
79
79
  # @example Run only the after save callbacks.
80
80
  # model.run_after_callbacks(:save)
81
81
  #
82
- # @param [ Array<Symbol> ] kinds The events that are occurring.
82
+ # @param [ Symbol... ] *kinds The events that are occurring.
83
83
  #
84
84
  # @return [ Object ] The result of the chain executing.
85
85
  def run_after_callbacks(*kinds)
@@ -96,7 +96,7 @@ module Mongoid
96
96
  # @example Run only the before save callbacks.
97
97
  # model.run_before_callbacks(:save, :create)
98
98
  #
99
- # @param [ Array<Symbol> ] kinds The events that are occurring.
99
+ # @param [ Symbol... ] *kinds The events that are occurring.
100
100
  #
101
101
  # @return [ Object ] The result of the chain executing.
102
102
  def run_before_callbacks(*kinds)
@@ -134,36 +134,126 @@ module Mongoid
134
134
  # Run the callbacks for embedded documents.
135
135
  #
136
136
  # @param [ Symbol ] kind The type of callback to execute.
137
- # @param [ Array<Document> ] children Children to exeute callbacks on. If
137
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
138
138
  # nil, callbacks will be executed on all cascadable children of
139
139
  # the document.
140
140
  #
141
141
  # @api private
142
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
143
+ if Mongoid::Config.around_callbacks_for_embeds
144
+ _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
145
+ else
146
+ _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
147
+ end
148
+ end
149
+
150
+ # Execute the callbacks of given kind for embedded documents including
151
+ # around callbacks.
152
+ #
153
+ # @note This method is prone to stack overflow errors if the document
154
+ # has a large number of embedded documents. It is recommended to avoid
155
+ # using around callbacks for embedded documents until a proper solution
156
+ # is implemented.
157
+ #
158
+ # @param [ Symbol ] kind The type of callback to execute.
159
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
160
+ # nil, callbacks will be executed on all cascadable children of
161
+ # the document.
162
+ #
163
+ # @api private
164
+ def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
143
165
  child, *tail = (children || cascadable_children(kind))
144
166
  if child.nil?
145
- return block&.call
167
+ block&.call
146
168
  elsif tail.empty?
147
- return child.run_callbacks(child_callback_type(kind, child), &block)
169
+ child.run_callbacks(child_callback_type(kind, child), &block)
148
170
  else
149
- return child.run_callbacks(child_callback_type(kind, child)) do
150
- _mongoid_run_child_callbacks(kind, children: tail, &block)
171
+ child.run_callbacks(child_callback_type(kind, child)) do
172
+ _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
151
173
  end
152
174
  end
153
175
  end
154
176
 
155
- # This is used to store callbacks to be executed later. A good use case for
156
- # this is delaying the after_find and after_initialize callbacks until the
157
- # associations are set on the document. This can also be used to delay
158
- # applying the defaults on a document.
177
+ # Execute the callbacks of given kind for embedded documents without
178
+ # around callbacks.
179
+ #
180
+ # @param [ Symbol ] kind The type of callback to execute.
181
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
182
+ # nil, callbacks will be executed on all cascadable children of
183
+ # the document.
184
+ #
185
+ # @api private
186
+ def _mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
187
+ children = (children || cascadable_children(kind))
188
+ callback_list = _mongoid_run_child_before_callbacks(kind, children: children)
189
+ return false if callback_list == false
190
+ value = block&.call
191
+ callback_list.each do |_next_sequence, env|
192
+ env.value &&= value
193
+ end
194
+ return false if _mongoid_run_child_after_callbacks(callback_list: callback_list) == false
195
+
196
+ value
197
+ end
198
+
199
+ # Execute the before callbacks of given kind for embedded documents.
200
+ #
201
+ # @param [ Symbol ] kind The type of callback to execute.
202
+ # @param [ Array<Document> ] children Children to execute callbacks on.
203
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
204
+ # pairs of callback sequence and environment. This list will be later used
205
+ # to execute after callbacks in reverse order.
159
206
  #
160
- # @return [ Array<Symbol> ] an array of symbols that represent the pending callbacks.
207
+ # @api private
208
+ def _mongoid_run_child_before_callbacks(kind, children: [], callback_list: [])
209
+ children.each do |child|
210
+ chain = child.__callbacks[child_callback_type(kind, child)]
211
+ env = ActiveSupport::Callbacks::Filters::Environment.new(child, false, nil)
212
+ next_sequence = compile_callbacks(chain)
213
+ unless next_sequence.final?
214
+ Mongoid.logger.warn("Around callbacks are disabled for embedded documents. Skipping around callbacks for #{child.class.name}.")
215
+ Mongoid.logger.warn("To enable around callbacks for embedded documents, set Mongoid::Config.around_callbacks_for_embeds to true.")
216
+ end
217
+ next_sequence.invoke_before(env)
218
+ return false if env.halted
219
+ env.value = !env.halted
220
+ callback_list << [next_sequence, env]
221
+ if (grandchildren = child.send(:cascadable_children, kind))
222
+ _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
223
+ end
224
+ end
225
+ callback_list
226
+ end
227
+
228
+ # Execute the after callbacks.
229
+ #
230
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
231
+ # pairs of callback sequence and environment.
232
+ def _mongoid_run_child_after_callbacks(callback_list: [])
233
+ callback_list.reverse_each do |next_sequence, env|
234
+ next_sequence.invoke_after(env)
235
+ return false if env.halted
236
+ end
237
+ end
238
+
239
+ # Returns the stored callbacks to be executed later.
240
+ #
241
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
161
242
  #
162
243
  # @api private
163
244
  def pending_callbacks
164
245
  @pending_callbacks ||= [].to_set
165
246
  end
166
247
 
248
+ # Stores callbacks to be executed later. A good use case for
249
+ # this is delaying the after_find and after_initialize callbacks until the
250
+ # associations are set on the document. This can also be used to delay
251
+ # applying the defaults on a document.
252
+ #
253
+ # @param [ Array<Symbol> ] value Method symbols of the pending callbacks to store.
254
+ #
255
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
256
+ #
167
257
  # @api private
168
258
  def pending_callbacks=(value)
169
259
  @pending_callbacks = value
@@ -298,7 +388,7 @@ module Mongoid
298
388
  end
299
389
  self.class.send :define_method, name do
300
390
  env = ActiveSupport::Callbacks::Filters::Environment.new(self, false, nil)
301
- sequence = chain.compile
391
+ sequence = compile_callbacks(chain)
302
392
  sequence.invoke_before(env)
303
393
  env.value = !env.halted
304
394
  sequence.invoke_after(env)
@@ -308,5 +398,24 @@ module Mongoid
308
398
  end
309
399
  send(name)
310
400
  end
401
+
402
+ # Compile the callback chain.
403
+ #
404
+ # This method hides the differences between ActiveSupport implementations
405
+ # before and after 7.1.
406
+ #
407
+ # @param [ ActiveSupport::Callbacks::CallbackChain ] chain The callback chain.
408
+ # @param [ Symbol | nil ] type The type of callback chain to compile.
409
+ #
410
+ # @return [ ActiveSupport::Callbacks::CallbackSequence ] The compiled callback sequence.
411
+ def compile_callbacks(chain, type = nil)
412
+ if chain.method(:compile).arity == 0
413
+ # ActiveSupport < 7.1
414
+ chain.compile
415
+ else
416
+ # ActiveSupport >= 7.1
417
+ chain.compile(type)
418
+ end
419
+ end
311
420
  end
312
421
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.0.5"
4
+ VERSION = "8.0.7"
5
5
  end
@@ -447,4 +447,24 @@ describe 'callbacks integration tests' do
447
447
  expect(saved_person.previously_persisted_value).to be_truthy
448
448
  end
449
449
  end
450
+
451
+ context 'cascade callbacks' do
452
+ ruby_version_gte '3.0'
453
+ config_override :around_callbacks_for_embeds, false
454
+
455
+ let(:book) do
456
+ Book.new
457
+ end
458
+
459
+ before do
460
+ 1500.times do
461
+ book.pages.build
462
+ end
463
+ end
464
+
465
+ # https://jira.mongodb.org/browse/MONGOID-5658
466
+ it 'does not raise SystemStackError' do
467
+ expect { book.save! }.not_to raise_error(SystemStackError)
468
+ end
469
+ end
450
470
  end
@@ -2711,4 +2711,31 @@ describe Mongoid::Attributes do
2711
2711
  catalog.set_field.should == Set.new([ 1, 2 ])
2712
2712
  end
2713
2713
  end
2714
+
2715
+ context 'when an embedded field has a capitalized store_as name' do
2716
+ let(:person) { Person.new(Purse: { brand: 'Gucci' }) }
2717
+
2718
+ it 'sets the value' do
2719
+ expect(person.purse.brand).to eq('Gucci')
2720
+ end
2721
+
2722
+ it 'saves successfully' do
2723
+ expect(person.save!).to eq(true)
2724
+ end
2725
+
2726
+ context 'when persisted' do
2727
+ before do
2728
+ person.save!
2729
+ person.reload
2730
+ end
2731
+
2732
+ it 'persists the value' do
2733
+ expect(person.reload.purse.brand).to eq('Gucci')
2734
+ end
2735
+
2736
+ it 'uses the correct key in the database' do
2737
+ expect(person.collection.find(_id: person.id).first['Purse']['_id']).to eq(person.purse.id)
2738
+ end
2739
+ end
2740
+ end
2714
2741
  end
@@ -272,6 +272,15 @@ describe Mongoid::Config do
272
272
  it_behaves_like "a config option"
273
273
  end
274
274
 
275
+ context 'when setting the allow_bson5_decimal128 option in the config' do
276
+ min_bson_version '5.0'
277
+
278
+ let(:option) { :allow_bson5_decimal128 }
279
+ let(:default) { false }
280
+
281
+ it_behaves_like "a config option"
282
+ end
283
+
275
284
  context 'when setting the broken_updates option in the config' do
276
285
  let(:option) { :broken_updates }
277
286
  let(:default) { false }
@@ -599,7 +608,7 @@ describe Mongoid::Config do
599
608
 
600
609
  it 'passes uuid to driver' do
601
610
  Mongo::Client.should receive(:new).with(SpecConfig.instance.addresses,
602
- auto_encryption_options: {
611
+ { auto_encryption_options: {
603
612
  'key_vault_namespace' => 'admin.datakeys',
604
613
  'kms_providers' => {'local' => {'key' => 'z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U'}},
605
614
  'schema_map' => {'blog_development.comments' => {
@@ -616,7 +625,7 @@ describe Mongoid::Config do
616
625
  platform: "mongoid-#{Mongoid::VERSION}",
617
626
  wrapping_libraries: [
618
627
  {'name' => 'Mongoid', 'version' => Mongoid::VERSION},
619
- ],
628
+ ]},
620
629
  )
621
630
 
622
631
  client