mongory 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/README.md +37 -7
- data/examples/benchmark-rails.rb +52 -0
- data/examples/benchmark.rb +79 -18
- data/lib/generators/mongory/matcher/matcher_generator.rb +1 -1
- data/lib/mongory/converters/abstract_converter.rb +22 -32
- data/lib/mongory/converters/condition_converter.rb +9 -19
- data/lib/mongory/converters/converted.rb +81 -0
- data/lib/mongory/converters/data_converter.rb +18 -7
- data/lib/mongory/converters/key_converter.rb +43 -19
- data/lib/mongory/converters/value_converter.rb +24 -19
- data/lib/mongory/converters.rb +1 -0
- data/lib/mongory/matchers/abstract_matcher.rb +94 -32
- data/lib/mongory/matchers/abstract_multi_matcher.rb +16 -45
- data/lib/mongory/matchers/and_matcher.rb +38 -10
- data/lib/mongory/matchers/array_record_matcher.rb +54 -28
- data/lib/mongory/matchers/elem_match_matcher.rb +13 -9
- data/lib/mongory/matchers/eq_matcher.rb +12 -7
- data/lib/mongory/matchers/every_matcher.rb +20 -9
- data/lib/mongory/matchers/exists_matcher.rb +15 -14
- data/lib/mongory/matchers/field_matcher.rb +58 -38
- data/lib/mongory/matchers/gt_matcher.rb +15 -7
- data/lib/mongory/matchers/gte_matcher.rb +15 -7
- data/lib/mongory/matchers/hash_condition_matcher.rb +54 -26
- data/lib/mongory/matchers/in_matcher.rb +20 -13
- data/lib/mongory/matchers/literal_matcher.rb +42 -48
- data/lib/mongory/matchers/lt_matcher.rb +15 -7
- data/lib/mongory/matchers/lte_matcher.rb +15 -7
- data/lib/mongory/matchers/ne_matcher.rb +12 -7
- data/lib/mongory/matchers/nin_matcher.rb +20 -12
- data/lib/mongory/matchers/not_matcher.rb +9 -5
- data/lib/mongory/matchers/or_matcher.rb +42 -13
- data/lib/mongory/matchers/present_matcher.rb +14 -15
- data/lib/mongory/matchers/regex_matcher.rb +37 -22
- data/lib/mongory/matchers/size_matcher.rb +50 -0
- data/lib/mongory/matchers.rb +1 -1
- data/lib/mongory/query_builder.rb +89 -27
- data/lib/mongory/query_matcher.rb +40 -13
- data/lib/mongory/query_operator.rb +1 -1
- data/lib/mongory/utils/context.rb +41 -0
- data/lib/mongory/utils/debugger.rb +6 -4
- data/lib/mongory/utils.rb +1 -0
- data/lib/mongory/version.rb +1 -1
- data/lib/mongory.rb +3 -3
- data/mongory.gemspec +3 -3
- metadata +11 -9
- data/lib/mongory/matchers/README.md +0 -57
- data/lib/mongory/matchers/abstract_operator_matcher.rb +0 -46
@@ -2,55 +2,49 @@
|
|
2
2
|
|
3
3
|
module Mongory
|
4
4
|
module Matchers
|
5
|
-
# LiteralMatcher
|
5
|
+
# LiteralMatcher handles direct value comparison with special array handling.
|
6
6
|
#
|
7
|
-
# This matcher
|
8
|
-
#
|
7
|
+
# This matcher is used when a condition is a literal value (not an operator).
|
8
|
+
# It handles both direct equality comparison and array-record scenarios.
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# For array records:
|
11
|
+
# - Uses ArrayRecordMatcher to check if any element matches
|
12
|
+
# For non-array records:
|
13
|
+
# - Uses appropriate matcher based on condition type (Hash, Regexp, nil, etc.)
|
11
14
|
#
|
12
|
-
# @example
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# { tags: [1, 2, 3] } # Array literal → ArrayRecordMatcher
|
17
|
-
# { email: /@gmail\\.com/i } # Regexp literal → RegexMatcher
|
18
|
-
# { info: nil } # nil literal → nil_matcher (matches null or missing)
|
15
|
+
# @example Basic equality matching
|
16
|
+
# matcher = LiteralMatcher.build(42)
|
17
|
+
# matcher.match?(42) #=> true
|
18
|
+
# matcher.match?([42, 43]) #=> true (array contains 42)
|
19
19
|
#
|
20
|
-
# @
|
21
|
-
#
|
20
|
+
# @example Regexp matching
|
21
|
+
# matcher = LiteralMatcher.build(/foo/)
|
22
|
+
# matcher.match?("foo") #=> true
|
23
|
+
# matcher.match?(["foobar"]) #=> true
|
22
24
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# - TrueClass / FalseClass
|
28
|
-
# - NilClass → delegates to nil_matcher
|
29
|
-
# - Regexp → delegates to RegexMatcher
|
30
|
-
# - Array → delegates to ArrayRecordMatcher
|
31
|
-
# - Hash → delegates to HashConditionMatcher (if treated as sub-query)
|
32
|
-
# - Other unrecognized values → fallback to equality match (==)
|
25
|
+
# @example Hash condition matching
|
26
|
+
# matcher = LiteralMatcher.build({ '$gt' => 10 })
|
27
|
+
# matcher.match?(15) #=> true
|
28
|
+
# matcher.match?([5, 15]) #=> true
|
33
29
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# - Nested paths like `"a.b.c"` → handled by FieldMatcher
|
37
|
-
# - Query combinators like `$or`, `$and`, `$not` → handled by corresponding matchers
|
38
|
-
#
|
39
|
-
# @see Mongory::Matchers::RegexMatcher
|
40
|
-
# @see Mongory::Matchers::OrMatcher
|
41
|
-
# @see Mongory::Matchers::ArrayRecordMatcher
|
42
|
-
# @see Mongory::Matchers::HashConditionMatcher
|
30
|
+
# @see AbstractMatcher
|
31
|
+
# @see ArrayRecordMatcher
|
43
32
|
class LiteralMatcher < AbstractMatcher
|
44
|
-
#
|
33
|
+
# Creates a raw Proc that performs the literal matching operation.
|
34
|
+
# The Proc handles both array and non-array records appropriately.
|
45
35
|
#
|
46
|
-
# @
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
36
|
+
# @return [Proc] a Proc that performs the literal matching operation
|
37
|
+
def raw_proc
|
38
|
+
array_record_proc = nil
|
39
|
+
dispatched_proc = dispatched_matcher.to_proc
|
40
|
+
|
41
|
+
Proc.new do |record|
|
42
|
+
if record.is_a?(Array)
|
43
|
+
array_record_proc ||= array_record_matcher.to_proc
|
44
|
+
array_record_proc.call(record)
|
45
|
+
else
|
46
|
+
dispatched_proc.call(record)
|
47
|
+
end
|
54
48
|
end
|
55
49
|
end
|
56
50
|
|
@@ -77,16 +71,16 @@ module Mongory
|
|
77
71
|
define_matcher(:dispatched) do
|
78
72
|
case @condition
|
79
73
|
when Hash
|
80
|
-
HashConditionMatcher.build(@condition)
|
74
|
+
HashConditionMatcher.build(@condition, context: @context)
|
81
75
|
when Regexp
|
82
|
-
RegexMatcher.build(@condition)
|
76
|
+
RegexMatcher.build(@condition, context: @context)
|
83
77
|
when nil
|
84
78
|
OrMatcher.build([
|
85
79
|
{ '$exists' => false },
|
86
80
|
{ '$eq' => nil }
|
87
|
-
])
|
81
|
+
], context: @context)
|
88
82
|
else
|
89
|
-
EqMatcher.build(@condition)
|
83
|
+
EqMatcher.build(@condition, context: @context)
|
90
84
|
end
|
91
85
|
end
|
92
86
|
|
@@ -96,7 +90,7 @@ module Mongory
|
|
96
90
|
# @return [ArrayRecordMatcher] the matcher used to match array-type records
|
97
91
|
# @!method array_record_matcher
|
98
92
|
define_matcher(:array_record) do
|
99
|
-
ArrayRecordMatcher.build(@condition)
|
93
|
+
ArrayRecordMatcher.build(@condition, context: @context)
|
100
94
|
end
|
101
95
|
|
102
96
|
# Validates the nested condition matcher, if applicable.
|
@@ -109,8 +103,8 @@ module Mongory
|
|
109
103
|
# Outputs the matcher tree by selecting either collection or condition matcher.
|
110
104
|
# Delegates `render_tree` to whichever submatcher was active.
|
111
105
|
#
|
112
|
-
# @param prefix [String]
|
113
|
-
# @param is_last [Boolean]
|
106
|
+
# @param prefix [String] the prefix string for tree rendering
|
107
|
+
# @param is_last [Boolean] whether this is the last node in the tree
|
114
108
|
# @return [void]
|
115
109
|
def render_tree(prefix = '', is_last: true)
|
116
110
|
super
|
@@ -6,7 +6,7 @@ module Mongory
|
|
6
6
|
#
|
7
7
|
# It returns true if the record is strictly less than the condition value.
|
8
8
|
#
|
9
|
-
# This matcher inherits from
|
9
|
+
# This matcher inherits from AbstractMatcher and uses the `<` operator.
|
10
10
|
#
|
11
11
|
# @example
|
12
12
|
# matcher = LtMatcher.build(10)
|
@@ -14,13 +14,21 @@ module Mongory
|
|
14
14
|
# matcher.match?(10) #=> false
|
15
15
|
# matcher.match?(11) #=> false
|
16
16
|
#
|
17
|
-
# @see
|
18
|
-
class LtMatcher <
|
19
|
-
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class LtMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the less-than comparison.
|
20
|
+
# The Proc uses the `<` operator to compare values.
|
20
21
|
#
|
21
|
-
# @return [
|
22
|
-
|
23
|
-
|
22
|
+
# @return [Proc] A proc that performs less-than comparison with error handling
|
23
|
+
# @note The proc includes error handling for invalid comparisons
|
24
|
+
def raw_proc
|
25
|
+
condition = @condition
|
26
|
+
|
27
|
+
Proc.new do |record|
|
28
|
+
record < condition
|
29
|
+
rescue StandardError
|
30
|
+
false
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
@@ -6,7 +6,7 @@ module Mongory
|
|
6
6
|
#
|
7
7
|
# It returns true if the record is less than or equal to the condition value.
|
8
8
|
#
|
9
|
-
# This matcher inherits from
|
9
|
+
# This matcher inherits from AbstractMatcher and uses the `<=` operator.
|
10
10
|
#
|
11
11
|
# @example
|
12
12
|
# matcher = LteMatcher.build(10)
|
@@ -14,13 +14,21 @@ module Mongory
|
|
14
14
|
# matcher.match?(10) #=> true
|
15
15
|
# matcher.match?(11) #=> false
|
16
16
|
#
|
17
|
-
# @see
|
18
|
-
class LteMatcher <
|
19
|
-
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class LteMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the less-than-or-equal comparison.
|
20
|
+
# The Proc uses the `<=` operator to compare values.
|
20
21
|
#
|
21
|
-
# @return [
|
22
|
-
|
23
|
-
|
22
|
+
# @return [Proc] A proc that performs less-than-or-equal comparison with error handling
|
23
|
+
# @note The proc includes error handling for invalid comparisons
|
24
|
+
def raw_proc
|
25
|
+
condition = @condition
|
26
|
+
|
27
|
+
Proc.new do |record|
|
28
|
+
record <= condition
|
29
|
+
rescue StandardError
|
30
|
+
false
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
@@ -6,7 +6,7 @@ module Mongory
|
|
6
6
|
#
|
7
7
|
# It returns true if the record is *not equal* to the condition.
|
8
8
|
#
|
9
|
-
# This matcher inherits its logic from
|
9
|
+
# This matcher inherits its logic from AbstractMatcher
|
10
10
|
# and uses Ruby's `!=` operator for comparison.
|
11
11
|
#
|
12
12
|
# @example
|
@@ -14,13 +14,18 @@ module Mongory
|
|
14
14
|
# matcher.match?(41) #=> true
|
15
15
|
# matcher.match?(42) #=> false
|
16
16
|
#
|
17
|
-
# @see
|
18
|
-
class NeMatcher <
|
19
|
-
#
|
17
|
+
# @see AbstractMatcher
|
18
|
+
class NeMatcher < AbstractMatcher
|
19
|
+
# Creates a raw Proc that performs the not-equal comparison.
|
20
|
+
# The Proc uses the `!=` operator to compare values.
|
20
21
|
#
|
21
|
-
# @return [
|
22
|
-
def
|
23
|
-
|
22
|
+
# @return [Proc] A proc that performs not-equal comparison
|
23
|
+
def raw_proc
|
24
|
+
condition = @condition
|
25
|
+
|
26
|
+
Proc.new do |record|
|
27
|
+
record != condition
|
28
|
+
end
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
@@ -24,25 +24,33 @@ module Mongory
|
|
24
24
|
#
|
25
25
|
# @see AbstractMatcher
|
26
26
|
class NinMatcher < AbstractMatcher
|
27
|
-
#
|
27
|
+
# Creates a raw Proc that performs the not-in matching operation.
|
28
|
+
# The Proc checks if the record has no elements in common with the condition array.
|
28
29
|
#
|
29
|
-
# @
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
# @return [Proc] A proc that performs not-in matching
|
31
|
+
def raw_proc
|
32
|
+
condition = @condition
|
33
|
+
|
34
|
+
Proc.new do |record|
|
35
|
+
if record.is_a?(Array)
|
36
|
+
return true if condition.is_a?(Range)
|
37
|
+
|
38
|
+
is_blank?(condition & record)
|
39
|
+
else
|
40
|
+
!condition.include?(record)
|
41
|
+
end
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
40
|
-
# Ensures the condition is a valid array.
|
45
|
+
# Ensures the condition is a valid array or range.
|
41
46
|
#
|
42
|
-
# @raise [TypeError] if the condition is not an array
|
47
|
+
# @raise [TypeError] if the condition is not an array nor a range
|
43
48
|
# @return [void]
|
44
49
|
def check_validity!
|
45
|
-
|
50
|
+
return if @condition.is_a?(Array)
|
51
|
+
return if @condition.is_a?(Range)
|
52
|
+
|
53
|
+
raise TypeError, '$nin needs an array'
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
@@ -18,12 +18,16 @@ module Mongory
|
|
18
18
|
#
|
19
19
|
# @see LiteralMatcher
|
20
20
|
class NotMatcher < LiteralMatcher
|
21
|
-
#
|
21
|
+
# Creates a raw Proc that performs the not-matching operation.
|
22
|
+
# The Proc inverts the result of the wrapped matcher.
|
22
23
|
#
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# @return [Proc] A proc that performs not-matching
|
25
|
+
def raw_proc
|
26
|
+
super_proc = super
|
27
|
+
|
28
|
+
Proc.new do |record|
|
29
|
+
!super_proc.call(record)
|
30
|
+
end
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
@@ -5,7 +5,8 @@ module Mongory
|
|
5
5
|
# OrMatcher implements the `$or` logical operator.
|
6
6
|
#
|
7
7
|
# It evaluates an array of subconditions and returns true
|
8
|
-
# if *any one* of them matches.
|
8
|
+
# if *any one* of them matches. For empty conditions, it returns false
|
9
|
+
# (using FALSE_PROC).
|
9
10
|
#
|
10
11
|
# Each subcondition is handled by a HashConditionMatcher with conversion disabled,
|
11
12
|
# since the parent matcher already manages data conversion.
|
@@ -20,24 +21,52 @@ module Mongory
|
|
20
21
|
# ])
|
21
22
|
# matcher.match?(record) #=> true if either condition matches
|
22
23
|
#
|
24
|
+
# @example Empty conditions
|
25
|
+
# matcher = OrMatcher.build([])
|
26
|
+
# matcher.match?(record) #=> false (uses FALSE_PROC)
|
27
|
+
#
|
23
28
|
# @see AbstractMultiMatcher
|
24
29
|
class OrMatcher < AbstractMultiMatcher
|
25
30
|
enable_unwrap!
|
26
|
-
|
27
|
-
#
|
28
|
-
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# @return [
|
32
|
-
def
|
33
|
-
|
31
|
+
|
32
|
+
# Creates a raw Proc that performs the or-matching operation.
|
33
|
+
# The Proc combines all submatcher Procs and returns true if any match.
|
34
|
+
# For empty conditions, returns FALSE_PROC.
|
35
|
+
#
|
36
|
+
# @return [Proc] a Proc that performs the or-matching operation
|
37
|
+
def raw_proc
|
38
|
+
return FALSE_PROC if matchers.empty?
|
39
|
+
|
40
|
+
combine_procs(*matchers.map(&:to_proc))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Recursively combines multiple matcher procs with OR logic.
|
44
|
+
# This method optimizes the combination of multiple matchers by building
|
45
|
+
# a balanced tree of OR operations.
|
46
|
+
#
|
47
|
+
# @param left [Proc] The left matcher proc to combine
|
48
|
+
# @param rest [Array<Proc>] The remaining matcher procs to combine
|
49
|
+
# @return [Proc] A new proc that combines all matchers with OR logic
|
50
|
+
# @example
|
51
|
+
# combine_procs(proc1, proc2, proc3)
|
52
|
+
# #=> proc { |record| proc1.call(record) || proc2.call(record) || proc3.call(record) }
|
53
|
+
def combine_procs(left, *rest)
|
54
|
+
return left if rest.empty?
|
55
|
+
|
56
|
+
right = combine_procs(*rest)
|
57
|
+
Proc.new do |record|
|
58
|
+
left.call(record) || right.call(record)
|
59
|
+
end
|
34
60
|
end
|
35
61
|
|
36
|
-
#
|
62
|
+
# Builds an array of matchers from the subconditions.
|
63
|
+
# Each subcondition is wrapped in a HashConditionMatcher.
|
37
64
|
#
|
38
|
-
# @return [
|
39
|
-
|
40
|
-
|
65
|
+
# @return [Array<AbstractMatcher>] array of submatchers
|
66
|
+
define_instance_cache_method(:matchers) do
|
67
|
+
@condition.map do |condition|
|
68
|
+
HashConditionMatcher.build(condition, context: @context)
|
69
|
+
end
|
41
70
|
end
|
42
71
|
|
43
72
|
# Ensures the condition is an array of hashes.
|
@@ -20,22 +20,19 @@ module Mongory
|
|
20
20
|
# matcher = PresentMatcher.build(false)
|
21
21
|
# matcher.match?(nil) #=> true
|
22
22
|
#
|
23
|
-
# @see
|
24
|
-
class PresentMatcher <
|
25
|
-
#
|
26
|
-
#
|
23
|
+
# @see AbstractMatcher
|
24
|
+
class PresentMatcher < AbstractMatcher
|
25
|
+
# Creates a raw Proc that performs the presence check.
|
26
|
+
# The Proc checks if the record's presence matches the condition.
|
27
27
|
#
|
28
|
-
# @
|
29
|
-
|
30
|
-
|
31
|
-
is_present?(super)
|
32
|
-
end
|
28
|
+
# @return [Proc] A proc that performs presence check
|
29
|
+
def raw_proc
|
30
|
+
condition = @condition
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
:==
|
32
|
+
Proc.new do |record|
|
33
|
+
record = nil if record == KEY_NOT_FOUND
|
34
|
+
is_present?(record) == condition
|
35
|
+
end
|
39
36
|
end
|
40
37
|
|
41
38
|
# Ensures that the condition value is a boolean.
|
@@ -43,7 +40,9 @@ module Mongory
|
|
43
40
|
# @raise [TypeError] if condition is not true or false
|
44
41
|
# @return [void]
|
45
42
|
def check_validity!
|
46
|
-
|
43
|
+
return if [true, false].include?(@condition)
|
44
|
+
|
45
|
+
raise TypeError, '$present needs a boolean'
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
@@ -12,41 +12,56 @@ module Mongory
|
|
12
12
|
# If a string is provided instead of a Regexp, it will be converted via `Regexp.new(...)`.
|
13
13
|
# This ensures consistent behavior for queries like `:field.regex => "foo"` and `:field.regex => /foo/`.
|
14
14
|
#
|
15
|
-
# @example
|
16
|
-
# matcher = RegexMatcher.build(
|
15
|
+
# @example Basic regex matching
|
16
|
+
# matcher = RegexMatcher.build(/^foo/)
|
17
17
|
# matcher.match?('foobar') #=> true
|
18
18
|
# matcher.match?('barfoo') #=> false
|
19
19
|
#
|
20
|
-
# @example
|
21
|
-
# RegexMatcher.build(/admin/i)
|
20
|
+
# @example Case-insensitive matching
|
21
|
+
# matcher = RegexMatcher.build(/admin/i)
|
22
|
+
# matcher.match?("ADMIN") #=> true
|
22
23
|
#
|
23
|
-
# @example
|
24
|
-
#
|
24
|
+
# @example String pattern
|
25
|
+
# matcher = RegexMatcher.build("^foo")
|
26
|
+
# matcher.match?("foobar") #=> true
|
25
27
|
#
|
26
|
-
# @
|
27
|
-
#
|
28
|
-
|
29
|
-
|
28
|
+
# @example Non-string input
|
29
|
+
# matcher = RegexMatcher.build(/\d+/)
|
30
|
+
# matcher.match?(123) #=> false (not a string)
|
31
|
+
#
|
32
|
+
# @see AbstractMatcher
|
33
|
+
class RegexMatcher < AbstractMatcher
|
34
|
+
# Initializes the matcher with a regex pattern.
|
35
|
+
# Converts string patterns to Regexp objects.
|
30
36
|
#
|
31
|
-
# @
|
32
|
-
|
33
|
-
|
37
|
+
# @param condition [String, Regexp] the regex pattern to match against
|
38
|
+
# @param context [Context] the query context
|
39
|
+
# @raise [TypeError] if condition is not a string or Regexp
|
40
|
+
def initialize(condition, context: Context.new)
|
41
|
+
super
|
42
|
+
@condition = Regexp.new(condition) if condition.is_a?(String)
|
34
43
|
end
|
35
44
|
|
36
|
-
#
|
37
|
-
#
|
45
|
+
# Creates a raw Proc that performs the regex matching operation.
|
46
|
+
# The Proc checks if the record is a string that matches the pattern.
|
47
|
+
# Returns false for non-string inputs or if the match fails.
|
38
48
|
#
|
39
|
-
# @
|
40
|
-
|
41
|
-
|
42
|
-
|
49
|
+
# @return [Proc] a Proc that performs regex matching
|
50
|
+
def raw_proc
|
51
|
+
condition = @condition
|
52
|
+
|
53
|
+
Proc.new do |record|
|
54
|
+
next false unless record.is_a?(String)
|
43
55
|
|
44
|
-
|
56
|
+
record.match?(condition)
|
57
|
+
rescue StandardError
|
58
|
+
false
|
59
|
+
end
|
45
60
|
end
|
46
61
|
|
47
|
-
# Ensures the condition is a
|
62
|
+
# Ensures the condition is a valid regex pattern (Regexp or String).
|
48
63
|
#
|
49
|
-
# @raise [TypeError] if condition is not a string
|
64
|
+
# @raise [TypeError] if condition is not a string or Regexp
|
50
65
|
# @return [void]
|
51
66
|
def check_validity!
|
52
67
|
return if @condition.is_a?(Regexp)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongory
|
4
|
+
module Matchers
|
5
|
+
# Matcher for the `$size` operator.
|
6
|
+
#
|
7
|
+
# This matcher expects the input to be an array, and delegates the comparison
|
8
|
+
# to a literal matcher using the array's size as the value.
|
9
|
+
#
|
10
|
+
# For example, the condition `{ tags: { '$size' => 3 } }` will match any
|
11
|
+
# document where `tags` is an array of length 3.
|
12
|
+
#
|
13
|
+
# ### Supported compound usages:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# Mongory.where(tags: { '$size' => 3 }) # exactly 3 elements
|
17
|
+
# Mongory.where(tags: { '$size' => { '$gt' => 1 } }) # more than 1
|
18
|
+
# Mongory.where(comments: { '$size' => { '$gt' => 1, '$lte' => 5 } }) # more than 1, up to 5 elements
|
19
|
+
# Mongory.where(tags: { '$size' => { '$in' => [1, 2, 3] } }) # 1, 2, or 3 elements
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# @see LiteralMatcher
|
23
|
+
#
|
24
|
+
# @note Ruby's Symbol class already defines a `#size` method,
|
25
|
+
# that will return the size of the symbol object.
|
26
|
+
# So, this is the only operator that cannot be used with
|
27
|
+
# the symbol snippet syntax (e.g. `:tags.size`).
|
28
|
+
#
|
29
|
+
# Use string key syntax instead: `:"tags.$size" => ...`
|
30
|
+
class SizeMatcher < LiteralMatcher
|
31
|
+
# Creates a raw Proc that performs the size matching operation.
|
32
|
+
#
|
33
|
+
# The returned Proc checks if the input is an Array. If so, it calculates
|
34
|
+
# the array's size and passes it to the wrapped literal matcher Proc.
|
35
|
+
#
|
36
|
+
# @return [Proc] A proc that performs size-based matching
|
37
|
+
def raw_proc
|
38
|
+
super_proc = super
|
39
|
+
|
40
|
+
Proc.new do |record|
|
41
|
+
next false unless record.is_a?(Array)
|
42
|
+
|
43
|
+
super_proc.call(record.size)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
register(:size, '$size', SizeMatcher)
|
49
|
+
end
|
50
|
+
end
|
data/lib/mongory/matchers.rb
CHANGED
@@ -153,7 +153,6 @@ end
|
|
153
153
|
|
154
154
|
require_relative 'matchers/abstract_matcher'
|
155
155
|
require_relative 'matchers/abstract_multi_matcher'
|
156
|
-
require_relative 'matchers/abstract_operator_matcher'
|
157
156
|
require_relative 'matchers/literal_matcher'
|
158
157
|
require_relative 'matchers/hash_condition_matcher'
|
159
158
|
require_relative 'matchers/and_matcher'
|
@@ -174,3 +173,4 @@ require_relative 'matchers/not_matcher'
|
|
174
173
|
require_relative 'matchers/or_matcher'
|
175
174
|
require_relative 'matchers/present_matcher'
|
176
175
|
require_relative 'matchers/regex_matcher'
|
176
|
+
require_relative 'matchers/size_matcher'
|