mongoid 7.0.3 → 7.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +1 -0
  4. data/LICENSE +1 -0
  5. data/README.md +3 -2
  6. data/Rakefile +12 -0
  7. data/lib/mongoid.rb +2 -1
  8. data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
  9. data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
  10. data/lib/mongoid/association/proxy.rb +1 -1
  11. data/lib/mongoid/association/relatable.rb +23 -21
  12. data/lib/mongoid/atomic.rb +13 -3
  13. data/lib/mongoid/atomic/paths/embedded.rb +1 -1
  14. data/lib/mongoid/attributes.rb +28 -20
  15. data/lib/mongoid/attributes/dynamic.rb +15 -14
  16. data/lib/mongoid/config/environment.rb +21 -8
  17. data/lib/mongoid/copyable.rb +5 -1
  18. data/lib/mongoid/criteria.rb +7 -1
  19. data/lib/mongoid/criteria/modifiable.rb +13 -2
  20. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
  21. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  22. data/lib/mongoid/criteria/queryable/key.rb +67 -8
  23. data/lib/mongoid/criteria/queryable/mergeable.rb +5 -4
  24. data/lib/mongoid/criteria/queryable/selectable.rb +3 -4
  25. data/lib/mongoid/criteria/queryable/selector.rb +9 -31
  26. data/lib/mongoid/extensions/hash.rb +4 -2
  27. data/lib/mongoid/extensions/regexp.rb +1 -1
  28. data/lib/mongoid/extensions/string.rb +5 -3
  29. data/lib/mongoid/fields.rb +2 -1
  30. data/lib/mongoid/matchable.rb +14 -15
  31. data/lib/mongoid/matchable/all.rb +4 -3
  32. data/lib/mongoid/matchable/default.rb +71 -24
  33. data/lib/mongoid/matchable/regexp.rb +2 -2
  34. data/lib/mongoid/persistable/pushable.rb +11 -2
  35. data/lib/mongoid/persistence_context.rb +6 -6
  36. data/lib/mongoid/positional.rb +1 -1
  37. data/lib/mongoid/query_cache.rb +3 -2
  38. data/lib/mongoid/validatable/macros.rb +1 -1
  39. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  40. data/lib/mongoid/version.rb +2 -1
  41. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
  42. data/spec/README.md +18 -0
  43. data/spec/app/models/delegating_patient.rb +16 -0
  44. data/spec/app/models/other_owner_object.rb +2 -0
  45. data/spec/integration/app_spec.rb +192 -0
  46. data/spec/integration/associations/embedded_spec.rb +62 -0
  47. data/spec/integration/criteria/time_with_zone_spec.rb +32 -0
  48. data/spec/integration/document_spec.rb +22 -0
  49. data/spec/integration/matchable_spec.rb +680 -0
  50. data/spec/lite_spec_helper.rb +15 -5
  51. data/spec/mongoid/association/embedded/embedded_in_spec.rb +58 -0
  52. data/spec/mongoid/association/embedded/embeds_many_models.rb +53 -0
  53. data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
  54. data/spec/mongoid/association/embedded/embeds_one_dnl_models.rb +6 -0
  55. data/spec/mongoid/association/embedded/embeds_one_models.rb +51 -0
  56. data/spec/mongoid/association/embedded/embeds_one_spec.rb +46 -0
  57. data/spec/mongoid/association/referenced/belongs_to_spec.rb +23 -6
  58. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +2 -1
  59. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
  60. data/spec/mongoid/association/referenced/has_one_spec.rb +12 -2
  61. data/spec/mongoid/attributes/dynamic_spec.rb +153 -0
  62. data/spec/mongoid/attributes_spec.rb +19 -7
  63. data/spec/mongoid/clients/factory_spec.rb +2 -2
  64. data/spec/mongoid/clients/options_spec.rb +4 -4
  65. data/spec/mongoid/clients/sessions_spec.rb +20 -7
  66. data/spec/mongoid/clients/transactions_spec.rb +36 -15
  67. data/spec/mongoid/clients_spec.rb +2 -2
  68. data/spec/mongoid/contextual/atomic_spec.rb +20 -10
  69. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  70. data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
  71. data/spec/mongoid/contextual/mongo_spec.rb +76 -53
  72. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  73. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +54 -0
  74. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
  75. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
  76. data/spec/mongoid/criteria/queryable/key_spec.rb +48 -6
  77. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +762 -0
  78. data/spec/mongoid/criteria/queryable/selectable_spec.rb +5 -224
  79. data/spec/mongoid/criteria/queryable/selector_spec.rb +37 -0
  80. data/spec/mongoid/criteria_spec.rb +7 -2
  81. data/spec/mongoid/document_fields_spec.rb +88 -0
  82. data/spec/mongoid/document_persistence_context_spec.rb +33 -0
  83. data/spec/mongoid/extensions/string_spec.rb +35 -7
  84. data/spec/mongoid/indexable_spec.rb +6 -4
  85. data/spec/mongoid/matchable/default_spec.rb +10 -3
  86. data/spec/mongoid/matchable/regexp_spec.rb +2 -2
  87. data/spec/mongoid/matchable_spec.rb +2 -2
  88. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  89. data/spec/mongoid/query_cache_spec.rb +2 -1
  90. data/spec/mongoid/relations/proxy_spec.rb +1 -1
  91. data/spec/mongoid/scopable_spec.rb +2 -1
  92. data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
  93. data/spec/mongoid/tasks/database_spec.rb +1 -1
  94. data/spec/mongoid/validatable/uniqueness_spec.rb +33 -6
  95. data/spec/spec_helper.rb +4 -37
  96. data/spec/support/child_process_helper.rb +76 -0
  97. data/spec/support/cluster_config.rb +158 -0
  98. data/spec/support/constraints.rb +29 -19
  99. data/spec/support/expectations.rb +17 -3
  100. data/spec/support/spec_config.rb +12 -4
  101. metadata +525 -464
  102. metadata.gz.sig +2 -0
@@ -58,7 +58,7 @@ module Mongoid
58
58
  #
59
59
  # @since 1.0.0
60
60
  def __numeric__(object)
61
- object.to_s =~ /(^[-+]?[0-9]+$)|(\.0+$)|(\.$)/ ? object.to_i : Float(object)
61
+ object.to_s =~ /(\A[-+]?[0-9]+\z)|(\.0+\z)|(\.\z)/ ? object.to_i : Float(object)
62
62
  end
63
63
 
64
64
  # Evolve the object to an integer.
@@ -10,7 +10,7 @@ module Mongoid
10
10
  # Is the object a regexp?
11
11
  #
12
12
  # @example Is the object a regex?
13
- # /^[123]/.regexp?
13
+ # /\A[123]/.regexp?
14
14
  #
15
15
  # @return [ true ] Always true.
16
16
  #
@@ -22,7 +22,7 @@ module Mongoid
22
22
  # Evolve the object into a regex.
23
23
  #
24
24
  # @example Evolve the object to a regex.
25
- # Regexp.evolve("^[123]")
25
+ # Regexp.evolve("\A[123]")
26
26
  #
27
27
  # @param [ Regexp, String ] object The object to evolve.
28
28
  #
@@ -53,7 +53,7 @@ module Mongoid
53
53
  # Evolve the object into a raw bson regex.
54
54
  #
55
55
  # @example Evolve the object to a regex.
56
- # BSON::Regexp::Raw.evolve("^[123]")
56
+ # BSON::Regexp::Raw.evolve("\\A[123]")
57
57
  #
58
58
  # @param [ BSON::Regexp::Raw, String ] object The object to evolve.
59
59
  #
@@ -3,16 +3,75 @@ module Mongoid
3
3
  class Criteria
4
4
  module Queryable
5
5
 
6
- # The key is a representation of a field in a queryable, that can be
7
- # expanded to special MongoDB selectors.
6
+ # Key objects represent specifications for building query expressions
7
+ # utilizing MongoDB selectors.
8
+ #
9
+ # Simple key-value conditions are translated directly into expression
10
+ # hashes by Mongoid without utilizing Key objects. For example, the
11
+ # following condition:
12
+ #
13
+ # Foo.where(price: 1)
14
+ #
15
+ # ... is translated to the following simple expression:
16
+ #
17
+ # {price: 1}
18
+ #
19
+ # More complex conditions would start involving Key objects. For example:
20
+ #
21
+ # Foo.where(:price.gt => 1)
22
+ #
23
+ # ... causes a Key instance to be created thusly:
24
+ #
25
+ # Key.new(:price, :__override__, '$gt')
26
+ #
27
+ # This Key instance utilizes +operator+ but not +expanded+ nor +block+.
28
+ # The corresponding MongoDB query expression is:
29
+ #
30
+ # {price: {'$gt' => 1}}
31
+ #
32
+ # A yet more more complex example is the following condition:
33
+ #
34
+ # Foo.geo_spacial(:boundary.intersects_point => [1, 10])
35
+ #
36
+ # Processing this condition will cause a Key instance to be created as
37
+ # follows:
38
+ #
39
+ # Key.new(:location, :__override__, '$geoIntersects', '$geometry') do |value|
40
+ # { "type" => POINT, "coordinates" => value }
41
+ # end
42
+ #
43
+ # ... eventually producing the following MongoDB query expression:
44
+ #
45
+ # {
46
+ # boundary: {
47
+ # '$geoIntersects' => {
48
+ # '$geometry' => {
49
+ # type: "Point" ,
50
+ # coordinates: [ 1, 10 ]
51
+ # }
52
+ # }
53
+ # }
54
+ # }
55
+ #
56
+ # Key instances can be thought of as procs that map a value to the
57
+ # MongoDB query expression required to obtain the key's condition,
58
+ # given the value.
8
59
  class Key
9
60
 
10
- # @attribute [r] name The name of the field.
11
- # @attribute [r] block The optional block to transform values.
12
- # @attribute [r] operator The MongoDB query operator.
13
- # @attribute [r] expanded The MongoDB expanded query operator.
14
- # @attribute [r] strategy The name of the merge strategy.
15
- attr_reader :block, :name, :operator, :expanded, :strategy
61
+ # @return [ String | Symbol ] The name of the field.
62
+ attr_reader :name
63
+
64
+ # @return [ String ] The MongoDB query operator.
65
+ attr_reader :operator
66
+
67
+ # @return [ String ] The MongoDB expanded query operator.
68
+ attr_reader :expanded
69
+
70
+ # @return [ Symbol ] The name of the merge strategy.
71
+ attr_reader :strategy
72
+
73
+ # @return [ Proc ] The optional block to transform values.
74
+ attr_reader :block
16
75
 
17
76
  # Does the key equal another object?
18
77
  #
@@ -45,7 +45,7 @@ module Mongoid
45
45
  use(:__union__)
46
46
  end
47
47
 
48
- # Reset the stratgies to nil, used after cloning.
48
+ # Clear the current strategy and negating flag, used after cloning.
49
49
  #
50
50
  # @example Reset the strategies.
51
51
  # mergeable.reset_strategies!
@@ -54,7 +54,8 @@ module Mongoid
54
54
  #
55
55
  # @since 1.0.0
56
56
  def reset_strategies!
57
- self.strategy, self.negating = nil, nil
57
+ self.strategy = nil
58
+ self.negating = nil
58
59
  end
59
60
 
60
61
  private
@@ -167,7 +168,7 @@ module Mongoid
167
168
  # @example Add the criterion.
168
169
  # mergeable.__override__([ 1, 2 ], "$in")
169
170
  #
170
- # @param [ Hash ] criterion The criteria.
171
+ # @param [ Hash | Criteria ] criterion The criteria.
171
172
  # @param [ String ] operator The MongoDB operator.
172
173
  #
173
174
  # @return [ Mergeable ] The new mergeable.
@@ -225,7 +226,7 @@ module Mongoid
225
226
  # @api private
226
227
  #
227
228
  # @example Add criterion with a strategy.
228
- # mergeable.with_strategy(:__union__, [ 1, 2, 3 ], "$in")
229
+ # mergeable.with_strategy(:__union__, {field_name: [ 1, 2, 3 ]}, "$in")
229
230
  #
230
231
  # @param [ Symbol ] strategy The name of the strategy method.
231
232
  # @param [ Object ] criterion The criterion to add.
@@ -24,7 +24,7 @@ module Mongoid
24
24
  # @since 2.0.0
25
25
  POLYGON = "Polygon"
26
26
 
27
- # @attribute [rw] negating If the next spression is negated.
27
+ # @attribute [rw] negating If the next expression is negated.
28
28
  # @attribute [rw] selector The query selector.
29
29
  attr_accessor :negating, :selector
30
30
 
@@ -434,7 +434,7 @@ module Mongoid
434
434
  # @since 1.0.0
435
435
  def not(*criterion)
436
436
  if criterion.empty?
437
- tap { |query| query.negating = true }
437
+ dup.tap { |query| query.negating = true }
438
438
  else
439
439
  __override__(criterion.first, "$not")
440
440
  end
@@ -630,8 +630,6 @@ module Mongoid
630
630
  # Take the provided criterion and store it as a selection in the query
631
631
  # selector.
632
632
  #
633
- # @api private
634
- #
635
633
  # @example Store the selection.
636
634
  # selectable.selection({ field: "value" })
637
635
  #
@@ -640,6 +638,7 @@ module Mongoid
640
638
  # @return [ Selectable ] The cloned selectable.
641
639
  #
642
640
  # @since 1.0.0
641
+ # @api private
643
642
  def selection(criterion = nil)
644
643
  clone.tap do |query|
645
644
  if criterion
@@ -21,9 +21,10 @@ module Mongoid
21
21
  other.each_pair do |key, value|
22
22
  if value.is_a?(Hash) && self[key.to_s].is_a?(Hash)
23
23
  value = self[key.to_s].merge(value) do |_key, old_val, new_val|
24
- if in?(_key)
24
+ case _key
25
+ when '$in'
25
26
  new_val & old_val
26
- elsif nin?(_key)
27
+ when '$nin'
27
28
  (old_val + new_val).uniq
28
29
  else
29
30
  new_val
@@ -52,10 +53,13 @@ module Mongoid
52
53
  def store(key, value)
53
54
  name, serializer = storage_pair(key)
54
55
  if multi_selection?(name)
55
- super(name, evolve_multi(value))
56
+ store_name = name
57
+ store_value = evolve_multi(value)
56
58
  else
57
- super(localized_key(name, serializer), evolve(serializer, value))
59
+ store_name = localized_key(name, serializer)
60
+ store_value = evolve(serializer, value)
58
61
  end
62
+ super(store_name, store_value)
59
63
  end
60
64
  alias :[]= :store
61
65
 
@@ -178,33 +182,7 @@ module Mongoid
178
182
  #
179
183
  # @since 1.0.0
180
184
  def multi_selection?(key)
181
- key =~ /\$and|\$or|\$nor/
182
- end
183
-
184
- # Determines if the selection operator takes a list. Returns true for $in and $nin.
185
- #
186
- # @api private
187
- #
188
- # @example Does the selection operator take multiple values?
189
- # selector.multi_value?("$nin")
190
- #
191
- # @param [ String ] key The key to check.
192
- #
193
- # @return [ true, false ] If the key is $in or $nin.
194
- #
195
- # @since 2.1.1
196
- def multi_value?(key)
197
- key =~ /\$nin|\$in/
198
- end
199
-
200
- private
201
-
202
- def in?(key)
203
- key =~ /\$in/
204
- end
205
-
206
- def nin?(key)
207
- key =~ /\$nin/
185
+ %w($and $or $nor).include?(key)
208
186
  end
209
187
  end
210
188
  end
@@ -46,9 +46,11 @@ module Mongoid
46
46
  value.each_pair do |_key, _value|
47
47
  value[_key] = (key == "$rename") ? _value.to_s : mongoize_for(key, klass, _key, _value)
48
48
  end
49
- (consolidated[key] ||= {}).merge!(value)
49
+ consolidated[key] ||= {}
50
+ consolidated[key].update(value)
50
51
  else
51
- (consolidated["$set"] ||= {}).merge!(key => mongoize_for(key, klass, key, value))
52
+ consolidated["$set"] ||= {}
53
+ consolidated["$set"].update(key => mongoize_for(key, klass, key, value))
52
54
  end
53
55
  end
54
56
  consolidated
@@ -9,7 +9,7 @@ module Mongoid
9
9
  # type.
10
10
  #
11
11
  # @example Mongoize the object.
12
- # Regexp.mongoize(/^[abc]/)
12
+ # Regexp.mongoize(/\A[abc]/)
13
13
  #
14
14
  # @param [ Regexp, String ] object The object to mongoize.
15
15
  #
@@ -69,7 +69,7 @@ module Mongoid
69
69
  #
70
70
  # @since 2.3.1
71
71
  def mongoid_id?
72
- self =~ /\A(|_)id$/
72
+ self =~ /\A(|_)id\z/
73
73
  end
74
74
 
75
75
  # Is the string a number? The literals "NaN", "Infinity", and "-Infinity"
@@ -82,7 +82,9 @@ module Mongoid
82
82
  #
83
83
  # @since 3.0.0
84
84
  def numeric?
85
- true if Float(self) rescue (self =~ /^NaN|\-?Infinity$/)
85
+ !!Float(self)
86
+ rescue ArgumentError
87
+ (self =~ /\A(?:NaN|-?Infinity)\z/) == 0
86
88
  end
87
89
 
88
90
  # Get the string as a getter string.
@@ -94,7 +96,7 @@ module Mongoid
94
96
  #
95
97
  # @since 1.0.0
96
98
  def reader
97
- delete("=").sub(/\_before\_type\_cast$/, '')
99
+ delete("=").sub(/\_before\_type\_cast\z/, '')
98
100
  end
99
101
 
100
102
  # Is this string a writer?
@@ -498,7 +498,8 @@ module Mongoid
498
498
  def create_translations_getter(name, meth)
499
499
  generated_methods.module_eval do
500
500
  re_define_method("#{meth}_translations") do
501
- (attributes[name] ||= {}).with_indifferent_access
501
+ attributes[name] ||= {}
502
+ attributes[name].with_indifferent_access
502
503
  end
503
504
  alias_method :"#{meth}_t", :"#{meth}_translations"
504
505
  end
@@ -2,6 +2,7 @@
2
2
  require "mongoid/matchable/default"
3
3
  require "mongoid/matchable/all"
4
4
  require "mongoid/matchable/and"
5
+ require "mongoid/matchable/elem_match"
5
6
  require "mongoid/matchable/eq"
6
7
  require "mongoid/matchable/exists"
7
8
  require "mongoid/matchable/gt"
@@ -11,15 +12,14 @@ require "mongoid/matchable/lt"
11
12
  require "mongoid/matchable/lte"
12
13
  require "mongoid/matchable/ne"
13
14
  require "mongoid/matchable/nin"
14
- require "mongoid/matchable/or"
15
15
  require "mongoid/matchable/nor"
16
- require "mongoid/matchable/size"
17
- require "mongoid/matchable/elem_match"
16
+ require "mongoid/matchable/or"
18
17
  require "mongoid/matchable/regexp"
18
+ require "mongoid/matchable/size"
19
19
 
20
20
  module Mongoid
21
21
 
22
- # This module contains all the behavior for ruby implementations of MongoDB
22
+ # This module contains all the behavior for Ruby implementations of MongoDB
23
23
  # selectors.
24
24
  #
25
25
  # @since 4.0.0
@@ -31,8 +31,8 @@ module Mongoid
31
31
  # @since 1.0.0
32
32
  MATCHERS = {
33
33
  "$all" => All,
34
- "$elemMatch" => ElemMatch,
35
34
  "$and" => And,
35
+ "$elemMatch" => ElemMatch,
36
36
  "$eq" => Eq,
37
37
  "$exists" => Exists,
38
38
  "$gt" => Gt,
@@ -42,8 +42,8 @@ module Mongoid
42
42
  "$lte" => Lte,
43
43
  "$ne" => Ne,
44
44
  "$nin" => Nin,
45
- "$or" => Or,
46
45
  "$nor" => Nor,
46
+ "$or" => Or,
47
47
  "$size" => Size,
48
48
  }.with_indifferent_access.freeze
49
49
 
@@ -64,13 +64,13 @@ module Mongoid
64
64
  value.each do |item|
65
65
  if item[0].to_s == "$not".freeze
66
66
  item = item[1]
67
- return false if matcher(self, key, item)._matches?(item)
67
+ return false if matcher(key, item)._matches?(item)
68
68
  else
69
- return false unless matcher(self, key, Hash[*item])._matches?(Hash[*item])
69
+ return false unless matcher(key, Hash[*item])._matches?(Hash[*item])
70
70
  end
71
71
  end
72
72
  else
73
- return false unless matcher(self, key, value)._matches?(value)
73
+ return false unless matcher(key, value)._matches?(value)
74
74
  end
75
75
  end
76
76
  true
@@ -81,20 +81,18 @@ module Mongoid
81
81
  # Get the matcher for the supplied key and value. Will determine the class
82
82
  # name from the key.
83
83
  #
84
- # @api private
85
- #
86
84
  # @example Get the matcher.
87
85
  # document.matcher(:title, { "$in" => [ "test" ] })
88
86
  #
89
- # @param [ Document ] document The document to check.
90
87
  # @param [ Symbol, String ] key The field name.
91
88
  # @param [ Object, Hash ] value The value or selector.
92
89
  #
93
90
  # @return [ Matcher ] The matcher.
94
91
  #
95
92
  # @since 2.0.0.rc.7
96
- def matcher(document, key, value)
97
- Matchable.matcher(document, key, value)
93
+ # @api private
94
+ def matcher(key, value)
95
+ Matchable.matcher(self, key, value)
98
96
  end
99
97
 
100
98
  class << self
@@ -105,7 +103,7 @@ module Mongoid
105
103
  # @api private
106
104
  #
107
105
  # @example Get the matcher.
108
- # document.matcher(:title, { "$in" => [ "test" ] })
106
+ # Matchable.matcher(document, :title, { "$in" => [ "test" ] })
109
107
  #
110
108
  # @param [ Document ] document The document to check.
111
109
  # @param [ Symbol, String ] key The field name.
@@ -149,6 +147,7 @@ module Mongoid
149
147
  # @return [ Object ] The value of the attribute.
150
148
  #
151
149
  # @since 2.2.1
150
+ # @api private
152
151
  def extract_attribute(document, key)
153
152
  if (key_string = key.to_s) =~ /.+\..+/
154
153
  key_string.split('.').inject(document.send(:as_attributes)) do |_attribs, _key|
@@ -10,11 +10,12 @@ module Mongoid
10
10
  # @example Do the values match?
11
11
  # matcher._matches?({ :key => 10 })
12
12
  #
13
- # @param [ Hash ] value The values to check.
13
+ # @param [ Hash ] condition The condition to evaluate. This must be
14
+ # a one-element hash like {'$gt' => 1}.
14
15
  #
15
16
  # @return [ true, false ] If the values match.
16
- def _matches?(value)
17
- first = first(value)
17
+ def _matches?(condition)
18
+ first = condition_value(condition)
18
19
  return false if first.is_a?(Array) && first.empty?
19
20
 
20
21
  attribute_array = Array.wrap(@attribute)
@@ -20,52 +20,99 @@ module Mongoid
20
20
  @attribute, @document = attribute, document
21
21
  end
22
22
 
23
- # Return true if the attribute and value are equal, or if it is an array
24
- # if the value is included.
23
+ # Checks whether the attribute matches the value, using the default
24
+ # MongoDB matching logic (i.e., when no operator is specified in the
25
+ # criteria).
25
26
  #
26
- # @example Does this value match?
27
- # default._matches?("value")
27
+ # If attribute and value are both of basic types like string or number,
28
+ # this method returns true if and only if the attribute equals the value.
28
29
  #
29
- # @param [ Object ] value The value to check if it matches.
30
+ # Value can also be of a type like Regexp or Range which defines
31
+ # more complex matching/inclusion behavior via the === operator.
32
+ # If so, and attribute is still of a basic type like string or number,
33
+ # this method returns true if and only if the value's === operator
34
+ # returns true for the attribute. For example, this method returns true
35
+ # if attribute is a string and value is a Regexp and attribute matches
36
+ # the value, of if attribute is a number and value is a Range and
37
+ # the value includes the attribute.
30
38
  #
31
- # @return [ true, false ] True if matches, false if not.
39
+ # If attribute is an array and value is not an array, the checks just
40
+ # described (i.e. the === operator invocation) are performed on each item
41
+ # of the attribute array. If any of the items in the attribute match
42
+ # the value according to the value type's === operator, this method
43
+ # returns true.
44
+ #
45
+ # If attribute and value are both arrays, this method returns true if and
46
+ # only if the arrays are equal (including the order of the elements).
47
+ #
48
+ # @param [ Object ] value The value to check.
49
+ #
50
+ # @return [ true, false ] True if attribute matches the value, false if not.
32
51
  #
33
52
  # @since 1.0.0
34
53
  def _matches?(value)
35
- attribute.is_a?(Array) && !value.is_a?(Array) ? attribute.any? { |_attribute| value === _attribute } : value === attribute
54
+ if attribute.is_a?(Array) && !value.is_a?(Array)
55
+ attribute.any? { |_attribute| value === _attribute }
56
+ else
57
+ value === attribute
58
+ end
36
59
  end
37
60
 
38
61
  protected
39
62
 
40
- # Convenience method for getting the first value in a hash.
63
+ # Given a condition, which is a one-element hash consisting of an
64
+ # operator and a value like {'$gt' => 1}, return the value.
41
65
  #
42
- # @example Get the first value.
43
- # matcher.first(:test => "value")
66
+ # @example Get the condition value.
67
+ # matcher.condition_value({'$gt' => 1})
68
+ # # => 1
44
69
  #
45
- # @param [ Hash ] hash The has to pull from.
70
+ # @param [ Hash ] condition The condition.
46
71
  #
47
- # @return [ Object ] The first value.
72
+ # @return [ Object ] The value of the condition.
48
73
  #
49
74
  # @since 1.0.0
50
- def first(hash)
51
- hash.values.first
75
+ def condition_value(condition)
76
+ unless condition.is_a?(Hash)
77
+ raise ArgumentError, 'Condition must be a hash'
78
+ end
79
+
80
+ unless condition.length == 1
81
+ raise ArgumentError, 'Condition must have one element'
82
+ end
83
+
84
+ condition.values.first
52
85
  end
53
86
 
54
- # If object exists then compare the two, otherwise return false
87
+ # Determines whether the attribute value stored in this matcher
88
+ # satisfies the provided condition using the provided operator.
89
+ #
90
+ # For example, given an instance of Gt matcher with the @attribute of
91
+ # 2, the matcher is set up to answer whether the attribute is
92
+ # greater than some input value. This input value is provided in
93
+ # the condition, which could be {"$gt" => 1}, and the operator is
94
+ # provided (somewhat in a duplicate fashion) in the operator argument,
95
+ # in this case :>.
55
96
  #
56
- # @example Determine if we can compare.
57
- # matcher.determine("test", "$in")
97
+ # @example
98
+ # matcher = Matchable::Gt.new(2)
99
+ # matcher.determine({'$gt' => 1}, :>)
100
+ # # => true
58
101
  #
59
- # @param [ Object ] value The value to compare with.
60
- # @param [ Symbol, String ] operator The comparison operation.
102
+ # @param [ Hash ] condition The condition to evaluate. This must be
103
+ # a one-element hash; the key is ignored, and the value is passed
104
+ # as the argument to the operator.
105
+ # @param [ Symbol, String ] operator The comparison operator or method.
106
+ # The operator is invoked on the attribute stored in the matcher
107
+ # instance.
61
108
  #
62
- # @return [ true, false ] The comparison or false.
109
+ # @return [ true, false ] Result of condition evaluation.
63
110
  #
64
111
  # @since 1.0.0
65
- def determine(value, operator)
66
- attribute.__array__.any? {|attr|
67
- attr ? attr.send(operator, first(value)) : false
68
- }
112
+ def determine(condition, operator)
113
+ attribute.__array__.any? do |attr|
114
+ attr && attr.send(operator, condition_value(condition))
115
+ end
69
116
  end
70
117
  end
71
118
  end