mongoid 8.0.5 → 8.0.7

Sign up to get free protection for your applications and to get access to all the features.
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