mongoid 7.1.0 → 7.1.6

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 (131) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +6 -6
  5. data/README.md +1 -1
  6. data/Rakefile +14 -5
  7. data/lib/config/locales/en.yml +5 -5
  8. data/lib/mongoid/association/accessors.rb +37 -2
  9. data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
  10. data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
  11. data/lib/mongoid/association/proxy.rb +1 -1
  12. data/lib/mongoid/association/referenced/belongs_to/binding.rb +1 -1
  13. data/lib/mongoid/association/referenced/belongs_to/eager.rb +38 -2
  14. data/lib/mongoid/association/referenced/eager.rb +29 -9
  15. data/lib/mongoid/association/referenced/has_one/proxy.rb +6 -1
  16. data/lib/mongoid/atomic.rb +13 -3
  17. data/lib/mongoid/clients/factory.rb +2 -2
  18. data/lib/mongoid/clients/options.rb +8 -8
  19. data/lib/mongoid/clients/sessions.rb +20 -4
  20. data/lib/mongoid/clients/storage_options.rb +5 -5
  21. data/lib/mongoid/config.rb +39 -9
  22. data/lib/mongoid/criteria.rb +23 -4
  23. data/lib/mongoid/criteria/modifiable.rb +2 -1
  24. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
  25. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +6 -6
  26. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +12 -0
  27. data/lib/mongoid/criteria/queryable/mergeable.rb +75 -8
  28. data/lib/mongoid/criteria/queryable/pipeline.rb +3 -2
  29. data/lib/mongoid/criteria/queryable/selectable.rb +120 -13
  30. data/lib/mongoid/criteria/queryable/storable.rb +104 -99
  31. data/lib/mongoid/errors/eager_load.rb +2 -0
  32. data/lib/mongoid/errors/no_client_config.rb +2 -2
  33. data/lib/mongoid/errors/no_default_client.rb +1 -1
  34. data/lib/mongoid/extensions/hash.rb +4 -2
  35. data/lib/mongoid/extensions/regexp.rb +1 -1
  36. data/lib/mongoid/fields.rb +2 -1
  37. data/lib/mongoid/fields/validators/macro.rb +4 -1
  38. data/lib/mongoid/matchable/regexp.rb +2 -2
  39. data/lib/mongoid/persistable/pushable.rb +11 -2
  40. data/lib/mongoid/persistence_context.rb +6 -6
  41. data/lib/mongoid/query_cache.rb +61 -18
  42. data/lib/mongoid/serializable.rb +9 -3
  43. data/lib/mongoid/tasks/database.rb +38 -3
  44. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  45. data/lib/mongoid/version.rb +1 -1
  46. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +32 -23
  47. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
  48. data/spec/app/models/coding.rb +4 -0
  49. data/spec/app/models/coding/pull_request.rb +12 -0
  50. data/spec/app/models/delegating_patient.rb +16 -0
  51. data/spec/app/models/passport.rb +1 -0
  52. data/spec/app/models/person.rb +2 -0
  53. data/spec/app/models/phone.rb +1 -0
  54. data/spec/app/models/publication.rb +5 -0
  55. data/spec/app/models/publication/encyclopedia.rb +12 -0
  56. data/spec/app/models/publication/review.rb +14 -0
  57. data/spec/app/models/series.rb +1 -0
  58. data/spec/app/models/wiki_page.rb +1 -0
  59. data/spec/integration/app_spec.rb +254 -0
  60. data/spec/integration/associations/embedded_spec.rb +54 -0
  61. data/spec/integration/associations/embeds_many_spec.rb +24 -0
  62. data/spec/integration/associations/embeds_one_spec.rb +24 -0
  63. data/spec/integration/associations/has_many_spec.rb +76 -0
  64. data/spec/integration/associations/has_one_spec.rb +76 -0
  65. data/spec/integration/bson_regexp_raw_spec.rb +20 -0
  66. data/spec/integration/criteria/date_field_spec.rb +41 -0
  67. data/spec/integration/criteria/logical_spec.rb +13 -0
  68. data/spec/integration/document_spec.rb +22 -0
  69. data/spec/integration/shardable_spec.rb +20 -4
  70. data/spec/lite_spec_helper.rb +12 -4
  71. data/spec/mongoid/association/accessors_spec.rb +238 -63
  72. data/spec/mongoid/association/embedded/embeds_many_models.rb +19 -0
  73. data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
  74. data/spec/mongoid/association/embedded/embeds_one_spec.rb +0 -2
  75. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +193 -10
  76. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +140 -1
  77. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +105 -0
  78. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
  79. data/spec/mongoid/clients/factory_spec.rb +8 -8
  80. data/spec/mongoid/clients/options_spec.rb +11 -11
  81. data/spec/mongoid/clients/sessions_spec.rb +8 -4
  82. data/spec/mongoid/clients/transactions_spec.rb +20 -8
  83. data/spec/mongoid/clients_spec.rb +2 -2
  84. data/spec/mongoid/contextual/atomic_spec.rb +22 -11
  85. data/spec/mongoid/contextual/geo_near_spec.rb +11 -2
  86. data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
  87. data/spec/mongoid/contextual/mongo_spec.rb +76 -53
  88. data/spec/mongoid/criteria/queryable/extensions/regexp_raw_spec.rb +1 -1
  89. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
  90. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
  91. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +19 -7
  92. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +28 -1
  93. data/spec/mongoid/criteria/queryable/mergeable_spec.rb +45 -12
  94. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +1051 -392
  95. data/spec/mongoid/criteria/queryable/selectable_spec.rb +52 -0
  96. data/spec/mongoid/criteria/queryable/storable_spec.rb +80 -2
  97. data/spec/mongoid/criteria_spec.rb +36 -2
  98. data/spec/mongoid/document_persistence_context_spec.rb +33 -0
  99. data/spec/mongoid/errors/no_client_config_spec.rb +2 -2
  100. data/spec/mongoid/errors/no_client_database_spec.rb +3 -3
  101. data/spec/mongoid/errors/no_client_hosts_spec.rb +3 -3
  102. data/spec/mongoid/fields_spec.rb +24 -1
  103. data/spec/mongoid/indexable_spec.rb +6 -4
  104. data/spec/mongoid/matchable/default_spec.rb +1 -1
  105. data/spec/mongoid/matchable/regexp_spec.rb +2 -2
  106. data/spec/mongoid/matchable_spec.rb +2 -2
  107. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  108. data/spec/mongoid/query_cache_spec.rb +77 -9
  109. data/spec/mongoid/relations/proxy_spec.rb +1 -1
  110. data/spec/mongoid/scopable_spec.rb +2 -1
  111. data/spec/mongoid/serializable_spec.rb +129 -18
  112. data/spec/mongoid/shardable_models.rb +1 -1
  113. data/spec/mongoid/shardable_spec.rb +2 -2
  114. data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
  115. data/spec/mongoid/tasks/database_spec.rb +1 -1
  116. data/spec/shared/LICENSE +20 -0
  117. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  118. data/spec/shared/lib/mrss/cluster_config.rb +211 -0
  119. data/spec/shared/lib/mrss/constraints.rb +312 -0
  120. data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
  121. data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
  122. data/spec/spec_helper.rb +2 -31
  123. data/spec/support/child_process_helper.rb +76 -0
  124. data/spec/support/cluster_config.rb +3 -3
  125. data/spec/support/constraints.rb +26 -10
  126. data/spec/support/expectations.rb +3 -1
  127. data/spec/support/helpers.rb +11 -0
  128. data/spec/support/session_registry.rb +50 -0
  129. data/spec/support/spec_config.rb +12 -4
  130. metadata +520 -473
  131. metadata.gz.sig +0 -0
@@ -17,67 +17,49 @@ module Mongoid
17
17
  # @api private
18
18
  module Storable
19
19
 
20
- # Adds an operator expression to the selector.
21
- #
22
- # This method takes the operator and the operator value expression
23
- # separately for callers' convenience. It can be considered to
24
- # handle storing the hash `{operator => op_expr}`.
25
- #
26
- # If the selector already has the specified operator in it (on the
27
- # top level), the new condition given in op_expr is added to the
28
- # existing conditions for the specified operator. This is
29
- # straightforward for $and; for other logical operators, the behavior
30
- # of this method is to add the new conditions to the existing operator.
31
- # For example, if the selector is currently:
32
- #
33
- # {'foo' => 'bar', '$or' => [{'hello' => 'world'}]}
34
- #
35
- # ... and operator is '$or' and op_expr is `{'test' => 123'}`,
36
- # the resulting selector will be:
37
- #
38
- # {'foo' => 'bar', '$or' => [{'hello' => 'world'}, {'test' => 123}]}
20
+ # Adds a field expression to the query.
39
21
  #
40
- # This does not implement an OR between the existing selector and the
41
- # new operator expression - handling this is the job of upstream
42
- # methods. This method simply stores op_expr into the selector on the
43
- # assumption that the existing selector is the correct left hand side
44
- # of the operation already.
22
+ # +field+ must be a field name, and it must be a string. The upstream
23
+ # code must have converted other field/key types to the simple string
24
+ # form by the time this method is invoked.
45
25
  #
46
- # For non-logical query-level operators like $where and $text, if
47
- # there already is a top-level operator with the same name, the
48
- # op_expr is added to the selector via a top-level $and operator,
49
- # thus producing a selector having both operator values.
26
+ # +value+ can be of any type, it is written into the selector unchanged.
50
27
  #
51
- # This method does not simplify values (i.e. if the selector is
52
- # currently empty and operator is $and, op_expr is written to the
53
- # selector with $and even if the $and can in principle be elided).
28
+ # This method performs no processing on the provided field value.
54
29
  #
55
- # This method mutates the receiver.
30
+ # Mutates the receiver.
56
31
  #
57
- # @param [ String ] operator The operator to add.
58
- # @param [ Hash ] op_expr Operator value to add.
32
+ # @param [ String ] field The field name.
33
+ # @param [ Object ] value The field value.
59
34
  #
60
35
  # @return [ Storable ] self.
61
- def add_operator_expression(operator, op_expr)
62
- unless operator.is_a?(String)
63
- raise ArgumentError, "Operator must be a string: #{operator}"
64
- end
65
-
66
- unless operator[0] == ?$
67
- raise ArgumentError, "Operator must begin with $: #{operator}"
36
+ def add_field_expression(field, value)
37
+ unless field.is_a?(String)
38
+ raise ArgumentError, "Field must be a string: #{field}"
68
39
  end
69
40
 
70
- if %w($and $nor $or).include?(operator)
71
- return add_logical_operator_expression(operator, op_expr)
41
+ if field[0] == ?$
42
+ raise ArgumentError, "Field cannot be an operator (i.e. begin with $): #{field}"
72
43
  end
73
44
 
74
- # For other operators, if the operator already exists in the
75
- # query, add the new condition with $and, otherwise add the
76
- # new condition to the top level.
77
- if selector[operator]
78
- add_logical_operator_expression('$and', [{operator => op_expr}])
45
+ if selector[field]
46
+ # We already have a restriction by the field we are trying
47
+ # to restrict, combine the restrictions.
48
+ if value.is_a?(Hash) && selector[field].is_a?(Hash) &&
49
+ value.keys.all? { |key|
50
+ key_s = key.to_s
51
+ key_s[0] == ?$ && !selector[field].key?(key_s)
52
+ }
53
+ then
54
+ # Multiple operators can be combined on the same field by
55
+ # adding them to the existing hash.
56
+ new_value = selector[field].merge(value)
57
+ selector.store(field, new_value)
58
+ else
59
+ add_operator_expression('$and', [{field => value}])
60
+ end
79
61
  else
80
- selector.store(operator, op_expr)
62
+ selector.store(field, value)
81
63
  end
82
64
 
83
65
  self
@@ -87,41 +69,40 @@ module Mongoid
87
69
  #
88
70
  # This method only handles logical operators ($and, $nor and $or).
89
71
  # It raises ArgumentError if called with another operator. Note that
90
- # in MongoDB, $not is a field-level operator and not a query-level one
91
- # $not is not handled by this method as a result.
72
+ # in MQL, $not is a field-level operator and not a query-level one,
73
+ # and therefore $not is not handled by this method.
92
74
  #
93
75
  # This method takes the operator and the operator value expression
94
76
  # separately for callers' convenience. It can be considered to
95
77
  # handle storing the hash `{operator => op_expr}`.
96
78
  #
97
- # If the selector already has the specified operator in it (on the
98
- # top level), the new condition given in op_expr is added to the
99
- # existing conditions for the specified operator. This is
100
- # straightforward for $and; for other logical operators, the behavior
101
- # of this method is to add the new conditions to the existing operator.
79
+ # If the selector consists of a single condition which is the specified
80
+ # operator (on the top level), the new condition given in op_expr is
81
+ # added to the existing conditions for the specified operator.
102
82
  # For example, if the selector is currently:
103
83
  #
104
- # {'foo' => 'bar', '$or' => [{'hello' => 'world'}]}
84
+ # {'$or' => [{'hello' => 'world'}]}
105
85
  #
106
- # ... and operator is '$or' and op_expr is `{'test' => 123'}`,
86
+ # ... and operator is '$or' and op_expr is `[{'test' => 123'}]`,
107
87
  # the resulting selector will be:
108
88
  #
109
- # {'foo' => 'bar', '$or' => [{'hello' => 'world'}, {'test' => 123}]}
89
+ # {'$or' => [{'hello' => 'world'}, {'test' => 123}]}
110
90
  #
111
- # This does not implement an OR between the existing selector and the
112
- # new operator expression - handling this is the job of upstream
113
- # methods. This method simply stores op_expr into the selector on the
114
- # assumption that the existing selector is the correct left hand side
115
- # of the operation already.
91
+ # This method always adds the new conditions as additional requirements;
92
+ # in other words, it does not implement the ActiveRecord or/nor behavior
93
+ # where the receiver becomes one of the operands. It is expected that
94
+ # code upstream of this method implements such behavior.
116
95
  #
117
96
  # This method does not simplify values (i.e. if the selector is
118
97
  # currently empty and operator is $and, op_expr is written to the
119
98
  # selector with $and even if the $and can in principle be elided).
99
+ # Such simplification is also expected to have already been performed
100
+ # by the upstream code.
120
101
  #
121
102
  # This method mutates the receiver.
122
103
  #
123
104
  # @param [ String ] operator The operator to add.
124
- # @param [ Hash ] op_expr Operator value to add.
105
+ # @param [ Array<Hash> ] op_expr Operator value to add.
125
106
  #
126
107
  # @return [ Storable ] self.
127
108
  def add_logical_operator_expression(operator, op_expr)
@@ -145,56 +126,80 @@ module Mongoid
145
126
  # with whatever other conditions exist.
146
127
  selector.store(operator, op_expr)
147
128
  else
148
- # Other operators need to operate explicitly on the previous
149
- # conditions and the new condition.
150
- new_value = [selector.to_hash.dup] + op_expr
151
- selector.replace(operator => new_value)
129
+ # Other operators need to be added separately
130
+ if selector[operator]
131
+ add_logical_operator_expression('$and', [operator => op_expr])
132
+ else
133
+ selector.store(operator, op_expr)
134
+ end
152
135
  end
153
136
 
154
137
  self
155
138
  end
156
139
 
157
- # Adds a field expression to the query.
140
+ # Adds an operator expression to the selector.
158
141
  #
159
- # field must be a field name, and it must be a string. The upstream
160
- # code must have converted other field/key types to the simple string
161
- # form by the time this method is invoked.
142
+ # This method takes the operator and the operator value expression
143
+ # separately for callers' convenience. It can be considered to
144
+ # handle storing the hash `{operator => op_expr}`.
162
145
  #
163
- # This method performs no processing on the provided field value.
146
+ # The operator value can be of any type.
164
147
  #
165
- # Mutates the receiver.
148
+ # If the selector already has the specified operator in it (on the
149
+ # top level), the new condition given in op_expr is added to the
150
+ # existing conditions for the specified operator. This is
151
+ # straightforward for $and; for other logical operators, the behavior
152
+ # of this method is to add the new conditions to the existing operator.
153
+ # For example, if the selector is currently:
166
154
  #
167
- # @param [ String ] field The field name.
168
- # @param [ Object ] value The field value.
155
+ # {'foo' => 'bar', '$or' => [{'hello' => 'world'}]}
156
+ #
157
+ # ... and operator is '$or' and op_expr is `{'test' => 123'}`,
158
+ # the resulting selector will be:
159
+ #
160
+ # {'foo' => 'bar', '$or' => [{'hello' => 'world'}, {'test' => 123}]}
161
+ #
162
+ # This does not implement an OR between the existing selector and the
163
+ # new operator expression - handling this is the job of upstream
164
+ # methods. This method simply stores op_expr into the selector on the
165
+ # assumption that the existing selector is the correct left hand side
166
+ # of the operation already.
167
+ #
168
+ # For non-logical query-level operators like $where and $text, if
169
+ # there already is a top-level operator with the same name, the
170
+ # op_expr is added to the selector via a top-level $and operator,
171
+ # thus producing a selector having both operator values.
172
+ #
173
+ # This method does not simplify values (i.e. if the selector is
174
+ # currently empty and operator is $and, op_expr is written to the
175
+ # selector with $and even if the $and can in principle be elided).
176
+ #
177
+ # This method mutates the receiver.
178
+ #
179
+ # @param [ String ] operator The operator to add.
180
+ # @param [ Object ] op_expr Operator value to add.
169
181
  #
170
182
  # @return [ Storable ] self.
171
- def add_field_expression(field, value)
172
- unless field.is_a?(String)
173
- raise ArgumentError, "Field must be a string: #{field}"
183
+ def add_operator_expression(operator, op_expr)
184
+ unless operator.is_a?(String)
185
+ raise ArgumentError, "Operator must be a string: #{operator}"
174
186
  end
175
187
 
176
- if field[0] == ?$
177
- raise ArgumentError, "Field cannot be an operator (i.e. begin with $): #{field}"
188
+ unless operator[0] == ?$
189
+ raise ArgumentError, "Operator must begin with $: #{operator}"
178
190
  end
179
191
 
180
- if selector[field]
181
- # We already have a restriction by the field we are trying
182
- # to restrict, combine the restrictions.
183
- if value.is_a?(Hash) && selector[field].is_a?(Hash) &&
184
- value.keys.all? { |key|
185
- key_s = key.to_s
186
- key_s[0] == ?$ && !selector[field].key?(key_s)
187
- }
188
- then
189
- # Multiple operators can be combined on the same field by
190
- # adding them to the existing hash.
191
- new_value = selector[field].merge(value)
192
- selector.store(field, new_value)
193
- else
194
- add_operator_expression('$and', [{field => value}])
195
- end
192
+ if %w($and $nor $or).include?(operator)
193
+ return add_logical_operator_expression(operator, op_expr)
194
+ end
195
+
196
+ # For other operators, if the operator already exists in the
197
+ # query, add the new condition with $and, otherwise add the
198
+ # new condition to the top level.
199
+ if selector[operator]
200
+ add_logical_operator_expression('$and', [{operator => op_expr}])
196
201
  else
197
- selector.store(field, value)
202
+ selector.store(operator, op_expr)
198
203
  end
199
204
 
200
205
  self
@@ -6,6 +6,8 @@ module Mongoid
6
6
 
7
7
  # This error is raised when attempting to eager load a many to many
8
8
  # association.
9
+ #
10
+ # @deprecated No longer used by Mongoid per MONGOID-4841.
9
11
  class EagerLoad < MongoidError
10
12
 
11
13
  # Create the new eager load error.
@@ -11,9 +11,9 @@ module Mongoid
11
11
  # Create the new error.
12
12
  #
13
13
  # @example Create the error.
14
- # NoClientConfig.new(:secondary)
14
+ # NoClientConfig.new(:analytics)
15
15
  #
16
- # @param [ String, Symbol ] name The name of the client.
16
+ # @param [ String | Symbol ] name The name of the client.
17
17
  #
18
18
  # @since 3.0.0
19
19
  def initialize(name)
@@ -10,7 +10,7 @@ module Mongoid
10
10
  # Create the new error with the defined client names.
11
11
  #
12
12
  # @example Create the new error.
13
- # NoDefaultClient.new([ :secondary ])
13
+ # NoDefaultClient.new([ :analytics ])
14
14
  #
15
15
  # @param [ Array<Symbol> ] keys The defined clients.
16
16
  #
@@ -48,9 +48,11 @@ module Mongoid
48
48
  value.each_pair do |_key, _value|
49
49
  value[_key] = (key == "$rename") ? _value.to_s : mongoize_for(key, klass, _key, _value)
50
50
  end
51
- (consolidated[key] ||= {}).merge!(value)
51
+ consolidated[key] ||= {}
52
+ consolidated[key].update(value)
52
53
  else
53
- (consolidated["$set"] ||= {}).merge!(key => mongoize_for(key, klass, key, value))
54
+ consolidated["$set"] ||= {}
55
+ consolidated["$set"].update(key => mongoize_for(key, klass, key, value))
54
56
  end
55
57
  end
56
58
  consolidated
@@ -11,7 +11,7 @@ module Mongoid
11
11
  # type.
12
12
  #
13
13
  # @example Mongoize the object.
14
- # Regexp.mongoize(/^[abc]/)
14
+ # Regexp.mongoize(/\A[abc]/)
15
15
  #
16
16
  # @param [ Regexp, String ] object The object to mongoize.
17
17
  #
@@ -500,7 +500,8 @@ module Mongoid
500
500
  def create_translations_getter(name, meth)
501
501
  generated_methods.module_eval do
502
502
  re_define_method("#{meth}_translations") do
503
- (attributes[name] ||= {}).with_indifferent_access
503
+ attributes[name] ||= {}
504
+ attributes[name].with_indifferent_access
504
505
  end
505
506
  alias_method :"#{meth}_t", :"#{meth}_translations"
506
507
  end
@@ -113,7 +113,10 @@ module Mongoid
113
113
  end
114
114
 
115
115
  if option == :type && options[option] == Symbol
116
- Mongoid.logger.warn(FIELD_TYPE_IS_SYMBOL)
116
+ @field_type_is_symbol_warned ||= begin
117
+ Mongoid.logger.warn(FIELD_TYPE_IS_SYMBOL)
118
+ true
119
+ end
117
120
  end
118
121
  end
119
122
  end
@@ -10,8 +10,8 @@ module Mongoid
10
10
  # Does the supplied query match the attribute?
11
11
  #
12
12
  # @example Does this match?
13
- # matcher._matches?(/^Em/)
14
- # matcher._matches?(BSON::Regex::Raw.new("^Em"))
13
+ # matcher._matches?(/\AEm/)
14
+ # matcher._matches?(BSON::Regex::Raw.new("\\AEm"))
15
15
  #
16
16
  # @param [ BSON::Regexp::Raw, Regexp ] regexp The regular expression object.
17
17
  #
@@ -24,7 +24,13 @@ module Mongoid
24
24
  def add_to_set(adds)
25
25
  prepare_atomic_operation do |ops|
26
26
  process_atomic_operations(adds) do |field, value|
27
- existing = send(field) || (attributes[field] ||= [])
27
+ existing = send(field) || attributes[field]
28
+ if existing.nil?
29
+ attributes[field] = []
30
+ # Read the value out of attributes:
31
+ # https://jira.mongodb.org/browse/MONGOID-4874
32
+ existing = attributes[field]
33
+ end
28
34
  values = [ value ].flatten(1)
29
35
  values.each do |val|
30
36
  existing.push(val) unless existing.include?(val)
@@ -51,7 +57,10 @@ module Mongoid
51
57
  def push(pushes)
52
58
  prepare_atomic_operation do |ops|
53
59
  process_atomic_operations(pushes) do |field, value|
54
- existing = send(field) || (attributes[field] ||= [])
60
+ existing = send(field) || begin
61
+ attributes[field] ||= []
62
+ attributes[field]
63
+ end
55
64
  values = [ value ].flatten(1)
56
65
  values.each{ |val| existing.push(val) }
57
66
  ops[atomic_attribute_name(field)] = { "$each" => values }
@@ -122,6 +122,12 @@ module Mongoid
122
122
  end
123
123
  end
124
124
 
125
+ def client_name
126
+ @client_name ||= options[:client] ||
127
+ Threaded.client_override ||
128
+ storage_options && __evaluate__(storage_options[:client])
129
+ end
130
+
125
131
  # Determine if this persistence context is equal to another.
126
132
  #
127
133
  # @example Compare two persistence contexts.
@@ -139,12 +145,6 @@ module Mongoid
139
145
 
140
146
  private
141
147
 
142
- def client_name
143
- @client_name ||= options[:client] ||
144
- Threaded.client_override ||
145
- storage_options && __evaluate__(storage_options[:client])
146
- end
147
-
148
148
  def set_options!(opts)
149
149
  @options ||= opts.each.reduce({}) do |_options, (key, value)|
150
150
  unless VALID_OPTIONS.include?(key.to_sym)
@@ -163,13 +163,13 @@ module Mongoid
163
163
  private
164
164
 
165
165
  def process(result)
166
- @remaining -= result.returned_count if limited?
167
- @cursor_id = result.cursor_id
168
- @coll_name ||= result.namespace.sub("#{database.name}.", '') if result.namespace
169
- documents = result.documents
166
+ documents = super
167
+
170
168
  if @cursor_id.zero? && !@after_first_batch
171
- (@cached_documents ||= []).concat(documents)
169
+ @cached_documents ||= []
170
+ @cached_documents.concat(documents)
172
171
  end
172
+
173
173
  @after_first_batch = true
174
174
  documents
175
175
  end
@@ -223,20 +223,40 @@ module Mongoid
223
223
  #
224
224
  # @since 5.0.0
225
225
  def each
226
- if system_collection? || !QueryCache.enabled?
226
+ if system_collection? || !QueryCache.enabled? || (respond_to?(:write?, true) && write?)
227
227
  super
228
228
  else
229
- unless cursor = cached_cursor
230
- read_with_retry do
231
- server = server_selector.select_server(cluster)
232
- cursor = CachedCursor.new(view, send_initial_query(server), server)
233
- QueryCache.cache_table[cache_key] = cursor
229
+ @cursor = nil
230
+ unless @cursor = cached_cursor
231
+
232
+ if driver_supports_cursor_sessions?
233
+ session = client.send(:get_session, @options)
234
+ read_with_retry(session, server_selector) do |server|
235
+ result = send_initial_query(server, session)
236
+ @cursor = get_cursor(result, server, session)
237
+ end
238
+ else
239
+ read_with_retry do
240
+ server = server_selector.select_server(cluster)
241
+ result = send_initial_query(server)
242
+ @cursor = get_cursor(result, server)
243
+ end
234
244
  end
235
245
  end
236
- cursor.each do |doc|
237
- yield doc
238
- end if block_given?
239
- cursor
246
+
247
+ if block_given?
248
+ if limit && limit != -1
249
+ @cursor.to_a[0...limit].each do |doc|
250
+ yield doc
251
+ end
252
+ else
253
+ @cursor.each do |doc|
254
+ yield doc
255
+ end
256
+ end
257
+ else
258
+ @cursor.to_enum
259
+ end
240
260
  end
241
261
  end
242
262
 
@@ -246,13 +266,30 @@ module Mongoid
246
266
  if limit
247
267
  key = [ collection.namespace, selector, nil, skip, sort, projection, collation ]
248
268
  cursor = QueryCache.cache_table[key]
249
- if cursor
250
- cursor.to_a[0...limit.abs]
251
- end
252
269
  end
253
270
  cursor || QueryCache.cache_table[cache_key]
254
271
  end
255
272
 
273
+ def get_cursor(result, server, session = nil)
274
+ if result.cursor_id == 0 || result.cursor_id.nil?
275
+ cursor = if session
276
+ CachedCursor.new(view, result, server, session: session)
277
+ else
278
+ CachedCursor.new(view, result, server)
279
+ end
280
+
281
+ QueryCache.cache_table[cache_key] = cursor
282
+ else
283
+ cursor = if session
284
+ Mongo::Cursor.new(view, result, server, session: session)
285
+ else
286
+ Mongo::Cursor.new(view, result, server)
287
+ end
288
+ end
289
+
290
+ cursor
291
+ end
292
+
256
293
  def cache_key
257
294
  [ collection.namespace, selector, limit, skip, sort, projection, collation ]
258
295
  end
@@ -260,6 +297,12 @@ module Mongoid
260
297
  def system_collection?
261
298
  collection.namespace =~ /\Asystem./
262
299
  end
300
+
301
+ def driver_supports_cursor_sessions?
302
+ # Driver versions 2.9 and newer support passing in a session to the
303
+ # cursor object.
304
+ (Mongo::VERSION.split('.').map(&:to_i) <=> [2, 9, 0]) > 0
305
+ end
263
306
  end
264
307
 
265
308
  # Adds behavior to the query cache for collections.