mongoid 7.2.1 → 7.3.1

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 (190) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/Rakefile +16 -0
  6. data/lib/config/locales/en.yml +2 -2
  7. data/lib/mongoid/association/accessors.rb +13 -1
  8. data/lib/mongoid/association/constrainable.rb +1 -1
  9. data/lib/mongoid/association/depending.rb +4 -4
  10. data/lib/mongoid/association/embedded/batchable.rb +1 -1
  11. data/lib/mongoid/association/embedded/embedded_in.rb +1 -1
  12. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +10 -3
  13. data/lib/mongoid/association/nested/many.rb +1 -1
  14. data/lib/mongoid/association/nested/one.rb +4 -2
  15. data/lib/mongoid/association/proxy.rb +6 -1
  16. data/lib/mongoid/association/referenced/auto_save.rb +2 -2
  17. data/lib/mongoid/association/referenced/has_many/enumerable.rb +493 -495
  18. data/lib/mongoid/association/referenced/has_many/proxy.rb +2 -2
  19. data/lib/mongoid/association/referenced/has_one/nested_builder.rb +2 -2
  20. data/lib/mongoid/attributes.rb +32 -14
  21. data/lib/mongoid/attributes/projector.rb +120 -0
  22. data/lib/mongoid/cacheable.rb +2 -2
  23. data/lib/mongoid/clients.rb +1 -1
  24. data/lib/mongoid/clients/factory.rb +22 -8
  25. data/lib/mongoid/config.rb +19 -2
  26. data/lib/mongoid/contextual/aggregable/mongo.rb +10 -8
  27. data/lib/mongoid/copyable.rb +1 -1
  28. data/lib/mongoid/criteria.rb +4 -5
  29. data/lib/mongoid/criteria/findable.rb +1 -1
  30. data/lib/mongoid/criteria/queryable/expandable.rb +0 -24
  31. data/lib/mongoid/criteria/queryable/extensions.rb +0 -4
  32. data/lib/mongoid/criteria/queryable/extensions/boolean.rb +1 -1
  33. data/lib/mongoid/criteria/queryable/mergeable.rb +46 -20
  34. data/lib/mongoid/criteria/queryable/selectable.rb +8 -8
  35. data/lib/mongoid/document.rb +1 -15
  36. data/lib/mongoid/errors/delete_restriction.rb +8 -9
  37. data/lib/mongoid/errors/mongoid_error.rb +1 -1
  38. data/lib/mongoid/evolvable.rb +1 -1
  39. data/lib/mongoid/extensions/boolean.rb +1 -2
  40. data/lib/mongoid/extensions/false_class.rb +1 -1
  41. data/lib/mongoid/extensions/hash.rb +2 -2
  42. data/lib/mongoid/extensions/true_class.rb +1 -1
  43. data/lib/mongoid/fields.rb +43 -5
  44. data/lib/mongoid/inspectable.rb +1 -1
  45. data/lib/mongoid/matcher.rb +26 -43
  46. data/lib/mongoid/matcher/bits.rb +41 -0
  47. data/lib/mongoid/matcher/bits_all_clear.rb +20 -0
  48. data/lib/mongoid/matcher/bits_all_set.rb +20 -0
  49. data/lib/mongoid/matcher/bits_any_clear.rb +20 -0
  50. data/lib/mongoid/matcher/bits_any_set.rb +20 -0
  51. data/lib/mongoid/matcher/elem_match.rb +2 -1
  52. data/lib/mongoid/matcher/expression.rb +9 -14
  53. data/lib/mongoid/matcher/field_expression.rb +4 -5
  54. data/lib/mongoid/matcher/field_operator.rb +6 -0
  55. data/lib/mongoid/matcher/mod.rb +17 -0
  56. data/lib/mongoid/matcher/type.rb +99 -0
  57. data/lib/mongoid/persistable/deletable.rb +1 -2
  58. data/lib/mongoid/persistable/destroyable.rb +8 -2
  59. data/lib/mongoid/persistable/updatable.rb +27 -2
  60. data/lib/mongoid/query_cache.rb +35 -29
  61. data/lib/mongoid/reloadable.rb +5 -0
  62. data/lib/mongoid/selectable.rb +5 -7
  63. data/lib/mongoid/shardable.rb +21 -5
  64. data/lib/mongoid/touchable.rb +23 -4
  65. data/lib/mongoid/validatable/associated.rb +1 -1
  66. data/lib/mongoid/validatable/presence.rb +3 -3
  67. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  68. data/lib/mongoid/version.rb +1 -1
  69. data/lib/rails/generators/mongoid/config/config_generator.rb +8 -1
  70. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +1 -1
  71. data/spec/integration/app_spec.rb +139 -82
  72. data/spec/integration/associations/embeds_many_spec.rb +44 -0
  73. data/spec/integration/associations/has_one_spec.rb +48 -0
  74. data/spec/integration/criteria/date_field_spec.rb +1 -1
  75. data/spec/integration/document_spec.rb +30 -0
  76. data/spec/integration/matcher_operator_data/bits_all_clear.yml +159 -0
  77. data/spec/integration/matcher_operator_data/bits_all_set.yml +159 -0
  78. data/spec/integration/matcher_operator_data/bits_any_clear.yml +159 -0
  79. data/spec/integration/matcher_operator_data/bits_any_set.yml +159 -0
  80. data/spec/integration/matcher_operator_data/comment.yml +22 -0
  81. data/spec/integration/matcher_operator_data/elem_match.yml +46 -0
  82. data/spec/integration/matcher_operator_data/implicit_traversal.yml +96 -0
  83. data/spec/integration/matcher_operator_data/in.yml +16 -0
  84. data/spec/integration/matcher_operator_data/mod.yml +55 -0
  85. data/spec/integration/matcher_operator_data/type.yml +70 -0
  86. data/spec/integration/matcher_operator_data/type_array.yml +16 -0
  87. data/spec/integration/matcher_operator_data/type_binary.yml +18 -0
  88. data/spec/integration/matcher_operator_data/type_boolean.yml +39 -0
  89. data/spec/integration/matcher_operator_data/type_code.yml +26 -0
  90. data/spec/integration/matcher_operator_data/type_code_with_scope.yml +26 -0
  91. data/spec/integration/matcher_operator_data/type_date.yml +39 -0
  92. data/spec/integration/matcher_operator_data/type_db_pointer.yml +19 -0
  93. data/spec/integration/matcher_operator_data/type_decimal.yml +40 -0
  94. data/spec/integration/matcher_operator_data/type_double.yml +15 -0
  95. data/spec/integration/matcher_operator_data/type_int32.yml +33 -0
  96. data/spec/integration/matcher_operator_data/type_int64.yml +33 -0
  97. data/spec/integration/matcher_operator_data/type_max_key.yml +17 -0
  98. data/spec/integration/matcher_operator_data/type_min_key.yml +17 -0
  99. data/spec/integration/matcher_operator_data/type_null.yml +23 -0
  100. data/spec/integration/matcher_operator_data/type_object.yml +23 -0
  101. data/spec/integration/matcher_operator_data/type_object_id.yml +25 -0
  102. data/spec/integration/matcher_operator_data/type_regex.yml +44 -0
  103. data/spec/integration/matcher_operator_data/type_string.yml +15 -0
  104. data/spec/integration/matcher_operator_data/type_symbol.yml +32 -0
  105. data/spec/integration/matcher_operator_data/type_timestamp.yml +25 -0
  106. data/spec/integration/matcher_operator_data/type_undefined.yml +17 -0
  107. data/spec/lite_spec_helper.rb +4 -3
  108. data/spec/mongoid/association/depending_spec.rb +391 -352
  109. data/spec/mongoid/association/nested/one_spec.rb +18 -14
  110. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +25 -8
  111. data/spec/mongoid/association/referenced/has_and_belongs_to_many/binding_spec.rb +1 -1
  112. data/spec/mongoid/association/referenced/has_many/binding_spec.rb +1 -1
  113. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +1 -1
  114. data/spec/mongoid/association/referenced/has_one_models.rb +8 -0
  115. data/spec/mongoid/atomic/paths_spec.rb +64 -12
  116. data/spec/mongoid/attributes/projector_data/embedded.yml +105 -0
  117. data/spec/mongoid/attributes/projector_data/fields.yml +93 -0
  118. data/spec/mongoid/attributes/projector_spec.rb +41 -0
  119. data/spec/mongoid/attributes_spec.rb +333 -0
  120. data/spec/mongoid/clients/factory_spec.rb +48 -0
  121. data/spec/mongoid/config_spec.rb +32 -0
  122. data/spec/mongoid/contextual/atomic_spec.rb +17 -4
  123. data/spec/mongoid/contextual/mongo_spec.rb +2 -2
  124. data/spec/mongoid/criteria/modifiable_spec.rb +1 -1
  125. data/spec/mongoid/criteria/queryable/expandable_spec.rb +0 -73
  126. data/spec/mongoid/criteria/queryable/extensions/boolean_spec.rb +1 -1
  127. data/spec/mongoid/criteria/queryable/mergeable_spec.rb +105 -7
  128. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +229 -24
  129. data/spec/mongoid/criteria/queryable/selectable_shared_examples.rb +39 -0
  130. data/spec/mongoid/criteria/queryable/selectable_spec.rb +1 -565
  131. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +590 -0
  132. data/spec/mongoid/criteria_projection_spec.rb +411 -0
  133. data/spec/mongoid/criteria_spec.rb +0 -275
  134. data/spec/mongoid/document_fields_spec.rb +26 -0
  135. data/spec/mongoid/document_query_spec.rb +51 -0
  136. data/spec/mongoid/document_spec.rb +13 -13
  137. data/spec/mongoid/errors/delete_restriction_spec.rb +1 -1
  138. data/spec/mongoid/errors/mongoid_error_spec.rb +20 -8
  139. data/spec/mongoid/extensions/false_class_spec.rb +1 -1
  140. data/spec/mongoid/extensions/string_spec.rb +5 -5
  141. data/spec/mongoid/extensions/true_class_spec.rb +1 -1
  142. data/spec/mongoid/fields/localized_spec.rb +4 -4
  143. data/spec/mongoid/fields_spec.rb +4 -4
  144. data/spec/mongoid/inspectable_spec.rb +12 -4
  145. data/spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml +104 -0
  146. data/spec/mongoid/matcher/extract_attribute_data/traversal.yml +68 -88
  147. data/spec/mongoid/matcher/extract_attribute_spec.rb +3 -13
  148. data/spec/mongoid/persistable/deletable_spec.rb +175 -1
  149. data/spec/mongoid/persistable/destroyable_spec.rb +191 -3
  150. data/spec/mongoid/persistable/savable_spec.rb +3 -5
  151. data/spec/mongoid/persistable/settable_spec.rb +30 -0
  152. data/spec/mongoid/persistable/upsertable_spec.rb +1 -1
  153. data/spec/mongoid/persistable_spec.rb +2 -2
  154. data/spec/mongoid/query_cache_middleware_spec.rb +8 -0
  155. data/spec/mongoid/reloadable_spec.rb +18 -1
  156. data/spec/mongoid/shardable_spec.rb +44 -0
  157. data/spec/mongoid/touchable_spec.rb +104 -16
  158. data/spec/mongoid/touchable_spec_models.rb +52 -0
  159. data/spec/mongoid/validatable_spec.rb +1 -1
  160. data/spec/shared/bin/get-mongodb-download-url +17 -0
  161. data/spec/shared/bin/s3-copy +45 -0
  162. data/spec/shared/bin/s3-upload +69 -0
  163. data/spec/shared/lib/mrss/cluster_config.rb +19 -4
  164. data/spec/shared/lib/mrss/constraints.rb +46 -8
  165. data/spec/shared/lib/mrss/docker_runner.rb +10 -1
  166. data/spec/shared/lib/mrss/lite_constraints.rb +16 -0
  167. data/spec/shared/lib/mrss/server_version_registry.rb +79 -33
  168. data/spec/shared/lib/mrss/spec_organizer.rb +32 -2
  169. data/spec/shared/lib/mrss/utils.rb +15 -0
  170. data/spec/shared/share/Dockerfile.erb +122 -29
  171. data/spec/shared/share/haproxy-1.conf +16 -0
  172. data/spec/shared/share/haproxy-2.conf +17 -0
  173. data/spec/shared/shlib/server.sh +58 -11
  174. data/spec/shared/shlib/set_env.sh +4 -1
  175. data/spec/spec_helper.rb +7 -3
  176. data/spec/support/client_registry.rb +9 -0
  177. data/spec/support/models/bolt.rb +8 -0
  178. data/spec/support/models/hole.rb +13 -0
  179. data/spec/support/models/mop.rb +9 -0
  180. data/spec/support/models/nut.rb +8 -0
  181. data/spec/support/models/person.rb +6 -0
  182. data/spec/support/models/sealer.rb +8 -0
  183. data/spec/support/models/shirt.rb +12 -0
  184. data/spec/support/models/spacer.rb +8 -0
  185. data/spec/support/models/threadlocker.rb +8 -0
  186. data/spec/support/models/washer.rb +8 -0
  187. data/spec/support/spec_config.rb +8 -0
  188. metadata +636 -528
  189. metadata.gz.sig +0 -0
  190. data/spec/support/cluster_config.rb +0 -158
@@ -0,0 +1,41 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module Bits
6
+ def matches?(exists, value, condition)
7
+ case value
8
+ when BSON::Binary
9
+ value = value.data.split('').map { |n| '%02x' % n.ord }.join.to_i(16)
10
+ end
11
+ case condition
12
+ when Array
13
+ array_matches?(value, condition)
14
+ when BSON::Binary
15
+ int_cond = condition.data.split('').map { |n| '%02x' % n.ord }.join.to_i(16)
16
+ int_matches?(value, int_cond)
17
+ when Integer
18
+ if condition < 0
19
+ raise Errors::InvalidQuery, "Invalid value for $#{operator_name} argument: negative integers are not allowed: #{condition}"
20
+ end
21
+ int_matches?(value, condition)
22
+ when Float
23
+ if (int_cond = condition.to_i).to_f == condition
24
+ if int_cond < 0
25
+ raise Errors::InvalidQuery, "Invalid value for $#{operator_name} argument: negative numbers are not allowed: #{condition}"
26
+ end
27
+ int_matches?(value, int_cond)
28
+ else
29
+ raise Errors::InvalidQuery, "Invalid type for $#{operator_name} argument: not representable as an integer: #{condition}"
30
+ end
31
+ else
32
+ raise Errors::InvalidQuery, "Invalid type for $#{operator_name} argument: #{condition}"
33
+ end
34
+ end
35
+
36
+ module_function def operator_name
37
+ name.sub(/.*::/, '').sub(/\A(.)/) { |l| l.downcase }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module BitsAllClear
6
+ include Bits
7
+ extend self
8
+
9
+ def array_matches?(value, condition)
10
+ condition.all? do |c|
11
+ value & (1<<c) == 0
12
+ end
13
+ end
14
+
15
+ def int_matches?(value, condition)
16
+ value & condition == 0
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module BitsAllSet
6
+ include Bits
7
+ extend self
8
+
9
+ def array_matches?(value, condition)
10
+ condition.all? do |c|
11
+ value & (1<<c) > 0
12
+ end
13
+ end
14
+
15
+ def int_matches?(value, condition)
16
+ value & condition == condition
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module BitsAnyClear
6
+ include Bits
7
+ extend self
8
+
9
+ def array_matches?(value, condition)
10
+ condition.any? do |c|
11
+ value & (1<<c) == 0
12
+ end
13
+ end
14
+
15
+ def int_matches?(value, condition)
16
+ value & condition < condition
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module BitsAnySet
6
+ include Bits
7
+ extend self
8
+
9
+ def array_matches?(value, condition)
10
+ condition.any? do |c|
11
+ value & (1<<c) > 0
12
+ end
13
+ end
14
+
15
+ def int_matches?(value, condition)
16
+ value & condition > 0
17
+ end
18
+ end
19
+ end
20
+ end
@@ -15,7 +15,8 @@ module Mongoid
15
15
  # Validate the condition is valid, even though we will never attempt
16
16
  # matching it.
17
17
  condition.each do |k, v|
18
- if k.to_s.start_with?('$')
18
+ k = k.to_s
19
+ if k.start_with?('$')
19
20
  begin
20
21
  ExpressionOperator.get(k)
21
22
  rescue Mongoid::Errors::InvalidExpressionOperator
@@ -12,25 +12,20 @@ module Mongoid
12
12
  end
13
13
  expr.all? do |k, expr_v|
14
14
  k = k.to_s
15
+ if k == "$comment"
16
+ # Nothing
17
+ return true
18
+ end
15
19
  if k.start_with?('$')
16
20
  ExpressionOperator.get(k).matches?(document, expr_v)
17
21
  else
18
- exists, value, expanded = Matcher.extract_attribute(document, k)
19
- # The value may have been expanded into an array, but then
20
- # array may have been shrunk back to a scalar (or hash) when
21
- # path contained a numeric position.
22
- # Do not treat a hash as an array here (both are iterable).
23
- if expanded && Array === value
24
- if value == []
25
- # Empty array is technically equivalent to exists: false.
26
- FieldExpression.matches?(false, nil, expr_v)
27
- else
28
- value.any? do |v|
29
- FieldExpression.matches?(true, v, expr_v)
30
- end
22
+ values = Matcher.extract_attribute(document, k)
23
+ if values.length > 0
24
+ values.any? do |v|
25
+ FieldExpression.matches?(true, v, expr_v)
31
26
  end
32
27
  else
33
- FieldExpression.matches?(exists, value, expr_v)
28
+ FieldExpression.matches?(false, nil, expr_v)
34
29
  end
35
30
  end
36
31
  end
@@ -36,14 +36,13 @@ module Mongoid
36
36
  FieldOperator.get(k).matches?(exists, value, cond_v)
37
37
  end
38
38
  elsif Hash === value
39
- sub_exists, sub_value, expanded =
40
- Matcher.extract_attribute(value, k)
41
- if expanded
42
- sub_value.any? do |sub_v|
39
+ sub_values = Matcher.extract_attribute(value, k)
40
+ if sub_values.length > 0
41
+ sub_values.any? do |sub_v|
43
42
  Eq.matches?(true, sub_v, cond_v)
44
43
  end
45
44
  else
46
- Eq.matches?(sub_exists, sub_value, cond_v)
45
+ Eq.matches?(false, nil, cond_v)
47
46
  end
48
47
  else
49
48
  false
@@ -5,6 +5,10 @@ module Mongoid
5
5
  module FieldOperator
6
6
  MAP = {
7
7
  '$all' => All,
8
+ '$bitsAllClear' => BitsAllClear,
9
+ '$bitsAllSet' => BitsAllSet,
10
+ '$bitsAnyClear' => BitsAnyClear,
11
+ '$bitsAnySet' => BitsAnySet,
8
12
  '$elemMatch' => ElemMatch,
9
13
  '$eq' => Eq,
10
14
  '$exists' => Exists,
@@ -13,11 +17,13 @@ module Mongoid
13
17
  '$in' => In,
14
18
  '$lt' => Lt,
15
19
  '$lte' => Lte,
20
+ '$mod' => Mod,
16
21
  '$nin' => Nin,
17
22
  '$ne' => Ne,
18
23
  '$not' => Not,
19
24
  '$regex' => Regex,
20
25
  '$size' => Size,
26
+ '$type' => Type,
21
27
  }.freeze
22
28
 
23
29
  module_function def get(op)
@@ -0,0 +1,17 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @api private
5
+ module Mod
6
+ module_function def matches?(exists, value, condition)
7
+ unless Array === condition
8
+ raise Errors::InvalidQuery, "Unknown $mod argument #{condition}"
9
+ end
10
+ if condition.length != 2
11
+ raise Errors::InvalidQuery, "Malformed $mod argument #{condition}, should have 2 elements"
12
+ end
13
+ condition[1] == value%condition[0]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,99 @@
1
+ module Mongoid
2
+ module Matcher
3
+
4
+ # @see https://docs.mongodb.com/manual/reference/operator/query/type/
5
+ #
6
+ # @api private
7
+ module Type
8
+ module_function def matches?(exists, value, condition)
9
+ conditions = case condition
10
+ when Array
11
+ condition
12
+ when Integer
13
+ [condition]
14
+ else
15
+ raise Errors::InvalidQuery, "Unknown $type argument: #{condition}"
16
+ end
17
+ conditions.each do |condition|
18
+ if one_matches?(exists, value, condition)
19
+ return true
20
+ end
21
+ end
22
+ false
23
+ end
24
+
25
+ module_function def one_matches?(exists, value, condition)
26
+ case condition
27
+ when 1
28
+ # Double
29
+ Float === value
30
+ when 2
31
+ # String
32
+ String === value
33
+ when 3
34
+ # Object
35
+ Hash === value
36
+ when 4
37
+ # Array
38
+ Array === value
39
+ when 5
40
+ # Binary data
41
+ BSON::Binary === value
42
+ when 6
43
+ # Undefined
44
+ BSON::Undefined === value
45
+ when 7
46
+ # ObjectId
47
+ BSON::ObjectId === value
48
+ when 8
49
+ # Boolean
50
+ TrueClass === value || FalseClass === value
51
+ when 9
52
+ # Date
53
+ Date === value || Time === value || DateTime === value
54
+ when 10
55
+ # Null
56
+ exists && NilClass === value
57
+ when 11
58
+ # Regex
59
+ Regexp::Raw === value || ::Regexp === value
60
+ when 12
61
+ # DBPointer deprecated
62
+ BSON::DbPointer === value
63
+ when 13
64
+ # JavaScript
65
+ BSON::Code === value
66
+ when 14
67
+ # Symbol deprecated
68
+ Symbol === value || BSON::Symbol::Raw === value
69
+ when 15
70
+ # Javascript with code deprecated
71
+ BSON::CodeWithScope === value
72
+ when 16
73
+ # 32-bit int
74
+ BSON::Int32 === value || Integer === value && (-2**32..2**32-1).include?(value)
75
+ when 17
76
+ # Timestamp
77
+ BSON::Timestamp === value
78
+ when 18
79
+ # Long
80
+ BSON::Int64 === value ||
81
+ Integer === value &&
82
+ (-2**64..2**64-1).include?(value) &&
83
+ !(-2**32..2**32-1).include?(value)
84
+ when 19
85
+ # Decimal
86
+ BSON::Decimal128 === value
87
+ when -1
88
+ # minKey
89
+ BSON::MinKey === value
90
+ when 127
91
+ # maxKey
92
+ BSON::MaxKey === value
93
+ else
94
+ raise Errors::InvalidQuery, "Unknown $type argument: #{condition}"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -47,7 +47,7 @@ module Mongoid
47
47
  #
48
48
  # @since 4.0.0
49
49
  def atomic_deletes
50
- { atomic_delete_modifier => { atomic_path => _index ? { "_id" => id } : true }}
50
+ { atomic_delete_modifier => { atomic_path => _index ? { "_id" => _id } : true }}
51
51
  end
52
52
 
53
53
  # Delete the embedded document.
@@ -117,7 +117,6 @@ module Mongoid
117
117
  #
118
118
  # @since 4.0.0
119
119
  def prepare_delete
120
- return false unless catch(:abort) { apply_delete_dependencies! }
121
120
  yield(self)
122
121
  freeze
123
122
  self.destroyed = true
@@ -23,13 +23,19 @@ module Mongoid
23
23
  def destroy(options = nil)
24
24
  raise Errors::ReadonlyDocument.new(self.class) if readonly?
25
25
  self.flagged_for_destroy = true
26
- result = run_callbacks(:destroy) { delete(options || {}) }
26
+ result = run_callbacks(:destroy) do
27
+ if catch(:abort) { apply_destroy_dependencies! }
28
+ delete(options || {})
29
+ else
30
+ false
31
+ end
32
+ end
27
33
  self.flagged_for_destroy = false
28
34
  result
29
35
  end
30
36
 
31
37
  def destroy!(options = {})
32
- destroy || raise(Errors::DocumentNotDestroyed.new(id, self.class))
38
+ destroy || raise(Errors::DocumentNotDestroyed.new(_id, self.class))
33
39
  end
34
40
 
35
41
  module ClassMethods
@@ -137,8 +137,33 @@ module Mongoid
137
137
  coll = collection(_root)
138
138
  selector = atomic_selector
139
139
  coll.find(selector).update_one(positionally(selector, updates), session: _session)
140
- conflicts.each_pair do |key, value|
141
- coll.find(selector).update_one(positionally(selector, { key => value }), session: _session)
140
+
141
+ # The following code applies updates which would cause
142
+ # path conflicts in MongoDB, for example when changing attributes
143
+ # of foo.0.bars while adding another foo. Each conflicting update
144
+ # is applied using its own write.
145
+ #
146
+ # TODO: MONGOID-5026: reduce the number of writes performed by
147
+ # more intelligently combining the writes such that there are
148
+ # fewer conflicts.
149
+ conflicts.each_pair do |modifier, changes|
150
+
151
+ # Group the changes according to their root key which is
152
+ # the top-level association name.
153
+ # This handles at least the cases described in MONGOID-4982.
154
+ conflicting_change_groups = changes.group_by do |key, _|
155
+ key.split(".", 2).first
156
+ end.values
157
+
158
+ # Apply changes in batches. Pop one change from each
159
+ # field-conflict group round-robin until all changes
160
+ # have been applied.
161
+ while batched_changes = conflicting_change_groups.map(&:pop).compact.to_h.presence
162
+ coll.find(selector).update_one(
163
+ positionally(selector, modifier => batched_changes),
164
+ session: _session,
165
+ )
166
+ end
142
167
  end
143
168
  end
144
169
  end
@@ -117,38 +117,44 @@ module Mongoid
117
117
  end
118
118
  end
119
119
 
120
- # The middleware to be added to a rack application in order to activate the
121
- # query cache.
122
- #
123
- # @since 4.0.0
124
- class Middleware
125
-
126
- # Instantiate the middleware.
127
- #
128
- # @example Create the new middleware.
129
- # Middleware.new(app)
130
- #
131
- # @param [ Object ] app The rack applciation stack.
120
+ if defined?(Mongo::QueryCache::Middleware)
121
+ Middleware = Mongo::QueryCache::Middleware
122
+ else
123
+ # The middleware to be added to a rack application in order to activate the
124
+ # query cache.
132
125
  #
133
126
  # @since 4.0.0
134
- def initialize(app)
135
- @app = app
136
- end
127
+ class Middleware
137
128
 
138
- # Execute the request, wrapping in a query cache.
139
- #
140
- # @example Execute the request.
141
- # middleware.call(env)
142
- #
143
- # @param [ Object ] env The environment.
144
- #
145
- # @return [ Object ] The result of the call.
146
- #
147
- # @since 4.0.0
148
- def call(env)
149
- QueryCache.cache { @app.call(env) }
150
- ensure
151
- QueryCache.clear_cache
129
+ # Instantiate the middleware.
130
+ #
131
+ # @example Create the new middleware.
132
+ # Middleware.new(app)
133
+ #
134
+ # @param [ Object ] app The rack application stack.
135
+ #
136
+ # @since 4.0.0
137
+ def initialize(app)
138
+ @app = app
139
+ end
140
+
141
+ # Execute the request, wrapping in a query cache.
142
+ #
143
+ # @example Execute the request.
144
+ # middleware.call(env)
145
+ #
146
+ # @param [ Object ] env The environment.
147
+ #
148
+ # @return [ Object ] The result of the call.
149
+ #
150
+ # @since 4.0.0
151
+ def call(env)
152
+ QueryCache.cache do
153
+ @app.call(env)
154
+ end
155
+ ensure
156
+ QueryCache.clear_cache
157
+ end
152
158
  end
153
159
  end
154
160