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
@@ -32,20 +32,20 @@ module Mongoid
32
32
  # @example Store this model by default in a different client.
33
33
  # class Band
34
34
  # include Mongoid::Document
35
- # store_in client: "secondary"
35
+ # store_in client: "analytics"
36
36
  # end
37
37
  #
38
38
  # @example Store this model with a combination of options.
39
39
  # class Band
40
40
  # include Mongoid::Document
41
- # store_in collection: "artists", database: "secondary"
41
+ # store_in collection: "artists", database: "music"
42
42
  # end
43
43
  #
44
44
  # @param [ Hash ] options The storage options.
45
45
  #
46
- # @option options [ String, Symbol ] :collection The collection name.
47
- # @option options [ String, Symbol ] :database The database name.
48
- # @option options [ String, Symbol ] :client The client name.
46
+ # @option options [ String | Symbol ] :collection The collection name.
47
+ # @option options [ String | Symbol ] :database The database name.
48
+ # @option options [ String | Symbol ] :client The client name.
49
49
  #
50
50
  # @return [ Class ] The model class.
51
51
  #
@@ -18,14 +18,31 @@ module Mongoid
18
18
 
19
19
  LOCK = Mutex.new
20
20
 
21
+ # Application name that is printed to the mongodb logs upon establishing
22
+ # a connection in server versions >= 3.4. Note that the name cannot
23
+ # exceed 128 bytes. It is also used as the database name if the
24
+ # database name is not explicitly defined.
25
+ option :app_name, default: nil
26
+
27
+ # Create indexes in background by default.
28
+ option :background_indexing, default: false
29
+
30
+ # Mark belongs_to associations as required by default, so that saving a
31
+ # model with a missing belongs_to association will trigger a validation
32
+ # error.
33
+ option :belongs_to_required_by_default, default: true
34
+
35
+ # Raise an exception when a field is redefined.
36
+ option :duplicate_fields_exception, default: false
37
+
38
+ # Include the root model name in json serialization.
21
39
  option :include_root_in_json, default: false
40
+
41
+ # # Include the _type field in serialization.
22
42
  option :include_type_for_serialization, default: false
23
- option :preload_models, default: false
24
- option :raise_not_found_error, default: true
25
- option :scope_overwrite_exception, default: false
26
- option :duplicate_fields_exception, default: false
27
- option :use_activesupport_time_zone, default: true
28
- option :use_utc, default: false
43
+
44
+ # Whether to join nested persistence contexts for atomic operations
45
+ # to parent contexts by default.
29
46
  option :join_contexts, default: false
30
47
 
31
48
  # The log level.
@@ -38,9 +55,22 @@ module Mongoid
38
55
  # configuration file is the log level given by this option honored.
39
56
  option :log_level, default: :info
40
57
 
41
- option :belongs_to_required_by_default, default: true
42
- option :app_name, default: nil
43
- option :background_indexing, default: false
58
+ # Preload all models in development, needed when models use inheritance.
59
+ option :preload_models, default: false
60
+
61
+ # Raise an error when performing a #find and the document is not found.
62
+ option :raise_not_found_error, default: true
63
+
64
+ # Raise an error when defining a scope with the same name as an
65
+ # existing method.
66
+ option :scope_overwrite_exception, default: false
67
+
68
+ # Use ActiveSupport's time zone in time operations instead of the
69
+ # Ruby default time zone.
70
+ option :use_activesupport_time_zone, default: true
71
+
72
+ # Return stored times as UTC.
73
+ option :use_utc, default: false
44
74
 
45
75
  # Has Mongoid been configured? This is checking that at least a valid
46
76
  # client config exists.
@@ -400,9 +400,22 @@ module Mongoid
400
400
  # @return [ Criteria ] The cloned selectable.
401
401
  #
402
402
  # @since 1.0.0
403
- def where(expression)
404
- if expression.is_a?(::String) && embedded?
405
- raise Errors::UnsupportedJavascript.new(klass, expression)
403
+ def where(*args)
404
+ # Historically this method required exactly one argument.
405
+ # As of https://jira.mongodb.org/browse/MONGOID-4804 it also accepts
406
+ # zero arguments.
407
+ # The underlying where implemetation that super invokes supports
408
+ # any number of arguments, but we don't presently allow mutiple
409
+ # arguments through this method. This API can be reconsidered in the
410
+ # future.
411
+ if args.length > 1
412
+ raise ArgumentError, "Criteria#where requires zero or one arguments (given #{args.length})"
413
+ end
414
+ if args.length == 1
415
+ expression = args.first
416
+ if expression.is_a?(::String) && embedded?
417
+ raise Errors::UnsupportedJavascript.new(klass, expression)
418
+ end
406
419
  end
407
420
  super
408
421
  end
@@ -437,7 +450,13 @@ module Mongoid
437
450
  #
438
451
  # @since 3.1.0
439
452
  def for_js(javascript, scope = {})
440
- js_query(BSON::CodeWithScope.new(javascript, scope))
453
+ code = if scope.empty?
454
+ # CodeWithScope is not supported for $where as of MongoDB 4.4
455
+ BSON::Code.new(javascript)
456
+ else
457
+ BSON::CodeWithScope.new(javascript, scope)
458
+ end
459
+ js_query(code)
441
460
  end
442
461
 
443
462
  private
@@ -74,7 +74,8 @@ module Mongoid
74
74
  # @since 5.1.0
75
75
  def create_with(attrs = {})
76
76
  tap do
77
- (@create_attrs ||= {}).merge!(attrs)
77
+ @create_attrs ||= {}
78
+ @create_attrs.update(attrs)
78
79
  end
79
80
  end
80
81
 
@@ -60,7 +60,7 @@ module Mongoid
60
60
  #
61
61
  # @since 1.0.0
62
62
  def __numeric__(object)
63
- object.to_s =~ /(^[-+]?[0-9]+$)|(\.0+$)|(\.$)/ ? object.to_i : Float(object)
63
+ object.to_s =~ /(\A[-+]?[0-9]+\z)|(\.0+\z)|(\.\z)/ ? object.to_i : Float(object)
64
64
  end
65
65
 
66
66
  # Evolve the object to an integer.
@@ -12,7 +12,7 @@ module Mongoid
12
12
  # Is the object a regexp?
13
13
  #
14
14
  # @example Is the object a regex?
15
- # /^[123]/.regexp?
15
+ # /\A[123]/.regexp?
16
16
  #
17
17
  # @return [ true ] Always true.
18
18
  #
@@ -24,7 +24,7 @@ module Mongoid
24
24
  # Evolve the object into a regex.
25
25
  #
26
26
  # @example Evolve the object to a regex.
27
- # Regexp.evolve("^[123]")
27
+ # Regexp.evolve("\A[123]")
28
28
  #
29
29
  # @param [ Regexp, String ] object The object to evolve.
30
30
  #
@@ -38,7 +38,7 @@ module Mongoid
38
38
  end
39
39
  end
40
40
 
41
- module Raw
41
+ module Raw_
42
42
 
43
43
  # Is the object a regexp?
44
44
  #
@@ -55,7 +55,7 @@ module Mongoid
55
55
  # Evolve the object into a raw bson regex.
56
56
  #
57
57
  # @example Evolve the object to a regex.
58
- # BSON::Regexp::Raw.evolve("^[123]")
58
+ # BSON::Regexp::Raw.evolve("\\A[123]")
59
59
  #
60
60
  # @param [ BSON::Regexp::Raw, String ] object The object to evolve.
61
61
  #
@@ -77,5 +77,5 @@ end
77
77
 
78
78
  ::Regexp.__send__(:include,Mongoid::Criteria::Queryable::Extensions::Regexp)
79
79
  ::Regexp.__send__(:extend, Mongoid::Criteria::Queryable::Extensions::Regexp::ClassMethods)
80
- BSON::Regexp::Raw.__send__(:include,Mongoid::Criteria::Queryable::Extensions::Regexp::Raw)
81
- BSON::Regexp::Raw.__send__(:extend, Mongoid::Criteria::Queryable::Extensions::Regexp::Raw::ClassMethods)
80
+ BSON::Regexp::Raw.__send__(:include,Mongoid::Criteria::Queryable::Extensions::Regexp::Raw_)
81
+ BSON::Regexp::Raw.__send__(:extend, Mongoid::Criteria::Queryable::Extensions::Regexp::Raw_::ClassMethods)
@@ -9,6 +9,18 @@ module Mongoid
9
9
  # This module contains additional time with zone behavior.
10
10
  module TimeWithZone
11
11
 
12
+ # Evolve the time as a date, UTC midnight.
13
+ #
14
+ # @example Evolve the time to a date query format.
15
+ # time.__evolve_date__
16
+ #
17
+ # @return [ Time ] The date at midnight UTC.
18
+ #
19
+ # @since 1.0.0
20
+ def __evolve_date__
21
+ ::Time.utc(year, month, day, 0, 0, 0, 0)
22
+ end
23
+
12
24
  # Evolve the time into a utc time.
13
25
  #
14
26
  # @example Evolve the time.
@@ -163,7 +163,7 @@ module Mongoid
163
163
  if expr.is_a?(Selectable)
164
164
  expr = expr.selector
165
165
  end
166
- normalized = _mongoid_normalize_expr(expr)
166
+ normalized = _mongoid_expand_keys(expr)
167
167
  sel.store(operator, result_criteria.push(normalized))
168
168
  end
169
169
  end
@@ -190,9 +190,9 @@ module Mongoid
190
190
  sel = query.selector
191
191
  _mongoid_flatten_arrays(criteria).each do |criterion|
192
192
  if criterion.is_a?(Selectable)
193
- expr = _mongoid_normalize_expr(criterion.selector)
193
+ expr = _mongoid_expand_keys(criterion.selector)
194
194
  else
195
- expr = criterion
195
+ expr = _mongoid_expand_keys(criterion)
196
196
  end
197
197
  if sel.empty?
198
198
  sel.store(operator, [expr])
@@ -212,7 +212,7 @@ module Mongoid
212
212
  # explicitly only expands Array objects and Array subclasses.
213
213
  private def _mongoid_flatten_arrays(array)
214
214
  out = []
215
- pending = array
215
+ pending = array.dup
216
216
  until pending.empty?
217
217
  item = pending.shift
218
218
  if item.nil?
@@ -226,11 +226,78 @@ module Mongoid
226
226
  out
227
227
  end
228
228
 
229
- # @api private
230
- private def _mongoid_normalize_expr(expr)
231
- expr.inject({}) do |hash, (field, value)|
232
- hash.merge!(field.__expr_part__(value.__expand_complex__))
229
+ # Takes a criteria hash and expands Key objects into hashes containing
230
+ # MQL corresponding to said key objects.
231
+ #
232
+ # Ruby does not permit multiple symbol operators. For example,
233
+ # {:foo.gt => 1, :foo.gt => 2} is collapsed to {:foo.gt => 2} by the
234
+ # language. Therefore this method never has to deal with multiple
235
+ # identical operators.
236
+ #
237
+ # Similarly, this method should never need to expand a literal value
238
+ # and an operator at the same time.
239
+ #
240
+ # @param [ Hash ] Criteria including Key instances.
241
+ #
242
+ # @return [ Hash ] Expanded criteria.
243
+ private def _mongoid_expand_keys(expr)
244
+ unless expr.is_a?(Hash)
245
+ raise ArgumentError, 'Argument must be a Hash'
246
+ end
247
+
248
+ result = {}
249
+ expr.each do |field, value|
250
+ field.__expr_part__(value.__expand_complex__).each do |k, v|
251
+ if result[k]
252
+ if result[k].is_a?(Hash)
253
+ # Existing value is an operator.
254
+ # If new value is also an operator, ensure there are no
255
+ # conflicts and add
256
+ if v.is_a?(Hash)
257
+ # The new value is also an operator.
258
+ # If there are no conflicts, combine the hashes, otherwise
259
+ # add new conditions to top level with $and.
260
+ if (v.keys & result[k].keys).empty?
261
+ result[k].update(v)
262
+ else
263
+ raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
264
+ result['$and'] ||= []
265
+ result['$and'] << {k => v}
266
+ end
267
+ else
268
+ # The new value is a simple value.
269
+ # If there isn't an $eq operator already in the query,
270
+ # transform the new value into an $eq operator and add it
271
+ # to the existing hash. Otherwise add the new condition
272
+ # with $and to the top level.
273
+ if result[k].key?('$eq')
274
+ raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
275
+ result['$and'] ||= []
276
+ result['$and'] << {k => v}
277
+ else
278
+ result[k].update('$eq' => v)
279
+ end
280
+ end
281
+ else
282
+ # Existing value is a simple value.
283
+ # If we are adding an operator, and the operator is not $eq,
284
+ # convert existing value into $eq and add the new operator
285
+ # to the same hash. Otherwise add the new condition with $and
286
+ # to the top level.
287
+ if v.is_a?(Hash) && !v.key?('$eq')
288
+ result[k] = {'$eq' => result[k]}.update(v)
289
+ else
290
+ raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
291
+ result['$and'] ||= []
292
+ result['$and'] << {k => v}
293
+ end
294
+ end
295
+ else
296
+ result[k] = v
297
+ end
298
+ end
233
299
  end
300
+ result
234
301
  end
235
302
 
236
303
  # Adds the criterion to the existing selection.
@@ -33,7 +33,7 @@ module Mongoid
33
33
  # Add a group operation to the aggregation pipeline.
34
34
  #
35
35
  # @example Add a group operation.
36
- # pipeline.group(:count.sum => 1, :max.max => "likes")
36
+ # pipeline.group(:_id => "foo", :count.sum => 1, :max.max => "likes")
37
37
  #
38
38
  # @param [ Hash ] entry The group entry.
39
39
  #
@@ -76,7 +76,8 @@ module Mongoid
76
76
  # pipeline.unwind(:field)
77
77
  # pipeline.unwind(document)
78
78
  #
79
- # @param [ String, Symbol, Hash ] field_or_doc The name of the field or a document.
79
+ # @param [ String, Symbol, Hash ] field_or_doc A field name or a
80
+ # document.
80
81
  #
81
82
  # @return [ Pipeline ] The pipeline.
82
83
  #
@@ -89,11 +89,27 @@ module Mongoid
89
89
  if new_s.is_a?(Selectable)
90
90
  new_s = new_s.selector
91
91
  end
92
- normalized = _mongoid_normalize_expr(new_s)
92
+ normalized = _mongoid_expand_keys(new_s)
93
93
  normalized.each do |k, v|
94
94
  k = k.to_s
95
- if c.selector[k] || k[0] == ?$
96
- c = c.send(:__multi__, [{k => v}], '$and')
95
+ if c.selector[k]
96
+ # There is already a condition on k.
97
+ # If v is an operator, and all existing conditions are
98
+ # also operators, and v isn't present in existing conditions,
99
+ # we can add to existing conditions.
100
+ # Otherwise use $and.
101
+ if v.is_a?(Hash) &&
102
+ v.length == 1 &&
103
+ (new_k = v.keys.first).start_with?('$') &&
104
+ (existing_kv = c.selector[k]).is_a?(Hash) &&
105
+ !existing_kv.key?(new_k) &&
106
+ existing_kv.keys.all? { |sub_k| sub_k.start_with?('$') }
107
+ then
108
+ merged_v = c.selector[k].merge(v)
109
+ c.selector.store(k, merged_v)
110
+ else
111
+ c = c.send(:__multi__, [k => v], '$and')
112
+ end
97
113
  else
98
114
  c.selector.store(k, v)
99
115
  end
@@ -567,17 +583,21 @@ module Mongoid
567
583
  if new_s.is_a?(Selectable)
568
584
  new_s = new_s.selector
569
585
  end
570
- new_s.each do |k, v|
586
+ _mongoid_expand_keys(new_s).each do |k, v|
571
587
  k = k.to_s
572
588
  if c.selector[k] || k[0] == ?$
573
589
  c = c.send(:__multi__, [{'$nor' => [{k => v}]}], '$and')
574
590
  else
575
- if v.is_a?(Regexp)
576
- negated_operator = '$not'
591
+ if v.is_a?(Hash)
592
+ c = c.send(:__multi__, [{'$nor' => [{k => v}]}], '$and')
577
593
  else
578
- negated_operator = '$ne'
594
+ if v.is_a?(Regexp)
595
+ negated_operator = '$not'
596
+ else
597
+ negated_operator = '$ne'
598
+ end
599
+ c = c.send(:__override__, {k => v}, negated_operator)
579
600
  end
580
- c = c.send(:__override__, {k => v}, negated_operator)
581
601
  end
582
602
  end
583
603
  c
@@ -586,13 +606,34 @@ module Mongoid
586
606
  end
587
607
  key :not, :override, "$not"
588
608
 
589
- # Adds $or selection to the selectable.
609
+ # Creates a disjunction using $or from the existing criteria in the
610
+ # receiver and the provided arguments.
611
+ #
612
+ # This behavior (receiver becoming one of the disjunction operands)
613
+ # matches ActiveRecord's +or+ behavior.
614
+ #
615
+ # Use +any_of+ to add a disjunction of the arguments as an additional
616
+ # constraint to the criteria already existing in the receiver.
617
+ #
618
+ # Each argument can be a Hash, a Criteria object, an array of
619
+ # Hash or Criteria objects, or a nested array. Nested arrays will be
620
+ # flattened and can be of any depth. Passing arrays is deprecated.
590
621
  #
591
- # @example Add the $or selection.
622
+ # @example Add the $or selection where both fields must have the specified values.
592
623
  # selectable.or(field: 1, field: 2)
593
624
  #
594
- # @param [ Array<Hash | Criteria> ] criteria Multiple key/value pair
595
- # matches or Criteria objects.
625
+ # @example Add the $or selection where either value match is sufficient.
626
+ # selectable.or({field: 1}, {field: 2})
627
+ #
628
+ # @example Same as previous example but using the deprecated array wrap.
629
+ # selectable.or([{field: 1}, {field: 2}])
630
+ #
631
+ # @example Same as previous example, also deprecated.
632
+ # selectable.or([{field: 1}], [{field: 2}])
633
+ #
634
+ # @param [ Hash | Criteria | Array<Hash | Criteria>, ... ] criteria
635
+ # Multiple key/value pair matches or Criteria objects, or arrays
636
+ # thereof. Passing arrays is deprecated.
596
637
  #
597
638
  # @return [ Selectable ] The new selectable.
598
639
  #
@@ -600,7 +641,73 @@ module Mongoid
600
641
  def or(*criteria)
601
642
  _mongoid_add_top_level_operation('$or', criteria)
602
643
  end
603
- alias :any_of :or
644
+
645
+ # Adds a disjunction of the arguments as an additional constraint
646
+ # to the criteria already existing in the receiver.
647
+ #
648
+ # Use +or+ to make the receiver one of the disjunction operands.
649
+ #
650
+ # Each argument can be a Hash, a Criteria object, an array of
651
+ # Hash or Criteria objects, or a nested array. Nested arrays will be
652
+ # flattened and can be of any depth. Passing arrays is deprecated.
653
+ #
654
+ # @example Add the $or selection where both fields must have the specified values.
655
+ # selectable.any_of(field: 1, field: 2)
656
+ #
657
+ # @example Add the $or selection where either value match is sufficient.
658
+ # selectable.any_of({field: 1}, {field: 2})
659
+ #
660
+ # @example Same as previous example but using the deprecated array wrap.
661
+ # selectable.any_of([{field: 1}, {field: 2}])
662
+ #
663
+ # @example Same as previous example, also deprecated.
664
+ # selectable.any_of([{field: 1}], [{field: 2}])
665
+ #
666
+ # @param [ Hash | Criteria | Array<Hash | Criteria>, ... ] criteria
667
+ # Multiple key/value pair matches or Criteria objects, or arrays
668
+ # thereof. Passing arrays is deprecated.
669
+ #
670
+ # @return [ Selectable ] The new selectable.
671
+ #
672
+ # @since 1.0.0
673
+ def any_of(*criteria)
674
+ criteria = _mongoid_flatten_arrays(criteria)
675
+ case criteria.length
676
+ when 0
677
+ clone
678
+ when 1
679
+ # When we have a single criteria, any_of behaves like and.
680
+ # Note: criteria can be a Query object, which #where method does
681
+ # not support.
682
+ self.and(*criteria)
683
+ else
684
+ # When we have multiple criteria, combine them all with $or
685
+ # and add the result to self.
686
+ exprs = criteria.map do |criterion|
687
+ if criterion.is_a?(Selectable)
688
+ _mongoid_expand_keys(criterion.selector)
689
+ else
690
+ Hash[criterion.map do |k, v|
691
+ if k.is_a?(Symbol)
692
+ [k.to_s, v]
693
+ else
694
+ [k, v]
695
+ end
696
+ end]
697
+ end
698
+ end
699
+ # Should be able to do:
700
+ #where('$or' => exprs)
701
+ # But since that is broken do instead:
702
+ clone.tap do |query|
703
+ if query.selector['$or']
704
+ query.selector.store('$or', query.selector['$or'] + exprs)
705
+ else
706
+ query.selector.store('$or', exprs)
707
+ end
708
+ end
709
+ end
710
+ end
604
711
 
605
712
  # Add a $size selection for array fields.
606
713
  #