scoped_search 4.1.11 → 4.1.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a3be22acffe6a33b115957197a5eca96b2e9cf2abe445b657b302663594abbe
4
- data.tar.gz: a97d2cd9a8db87347dee308b1340e87967a887dd6b3c5c7b2124f7d9859a9843
3
+ metadata.gz: fbd1e857e3b248d6d585ac7c41679b536949dc945f4c7e067b2b16e86ce325b2
4
+ data.tar.gz: b4c1dc92102ff1d56b1f23406b16cf0523c6bdc17ab336ee92300993de5fd7f9
5
5
  SHA512:
6
- metadata.gz: 840841fa78847d12b0f71ef620dbc73905e34a48325e10864ac56bf4aa746659d12f5cbbb18847c660e019bd42700700afb04928e7ccd48871bfe427ca8ba824
7
- data.tar.gz: 11839160ebde29751173c28c718546d92a4378c316f081632f18eef2d688d569a4f1277caf14c40c4896f2168a7e4f42d0717af7482d430de13e9b04b3e77b9a
6
+ metadata.gz: 1fe965444b5640251c4ca34c5fc1fca34857f2c8cc96a8f759096165391490ca7deb7f5b3f9d8ed8c9219a5ddfa82716660e2739adb5d7dfdb2d61ca95a968a7
7
+ data.tar.gz: f9ce7c87b95ceec5f3f5d1cc46f6401ef121870bf58a8cffe1b07cb7de597b8bcd3bcd1069e98ef52007df2209b26998f3f1c05a9719eefe17351d1f6da1eebf
@@ -509,7 +509,7 @@ module ScopedSearch
509
509
  raise ScopedSearch::QueryNotSupported, "Field '#{lhs.value}' not recognized for searching!" unless field
510
510
 
511
511
  # see if the value passes user defined validation
512
- if operator == :in
512
+ if [:in, :notin].include?(operator)
513
513
  rhs.value.split(',').each { |v| validate_value(field, v) }
514
514
  else
515
515
  validate_value(field, rhs.value)
@@ -31,7 +31,7 @@ module ScopedSearch::QueryLanguage::Parser
31
31
  next_token if !root_node && peek_token == :lparen # skip starting :lparen
32
32
  expressions << parse_logical_expression until peek_token.nil? || peek_token == :rparen
33
33
  next_token if !root_node && peek_token == :rparen # skip final :rparen
34
-
34
+
35
35
  return ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(DEFAULT_SEQUENCE_OPERATOR, expressions, root_node)
36
36
  end
37
37
 
@@ -42,18 +42,23 @@ module ScopedSearch::QueryLanguage::Parser
42
42
  when :lparen; parse_expression_sequence
43
43
  when :not; parse_logical_not_expression
44
44
  when :null, :notnull; parse_null_expression
45
+ when *LOGICAL_INFIX_OPERATORS; parse_logical_infix_expression
45
46
  else; parse_comparison
46
47
  end
47
48
 
48
49
  if LOGICAL_INFIX_OPERATORS.include?(peek_token)
49
- operator = next_token
50
- rhs = parse_logical_expression
51
- ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(operator, [lhs, rhs])
50
+ parse_logical_infix_expression([lhs])
52
51
  else
53
52
  lhs
54
53
  end
55
54
  end
56
55
 
56
+ def parse_logical_infix_expression(previous = [])
57
+ operator = next_token
58
+ rhs = parse_logical_expression
59
+ ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(operator, previous + [rhs])
60
+ end
61
+
57
62
  # Parses a NOT expression
58
63
  def parse_logical_not_expression
59
64
  next_token # = skip NOT operator
@@ -80,7 +85,24 @@ module ScopedSearch::QueryLanguage::Parser
80
85
 
81
86
  # Parses a prefix comparison, i.e. without an explicit field: <operator> <value>
82
87
  def parse_prefix_comparison
83
- return ScopedSearch::QueryLanguage::AST::OperatorNode.new(next_token, [parse_value])
88
+ token = next_token
89
+ case token
90
+ when :in
91
+ parse_prefix_in(true)
92
+ when :notin
93
+ parse_prefix_in(false)
94
+ else
95
+ ScopedSearch::QueryLanguage::AST::OperatorNode.new(token, [parse_value])
96
+ end
97
+ end
98
+
99
+ def parse_prefix_in(inclusion)
100
+ cmp, log = inclusion ? [:eq, :or] : [:ne, :and]
101
+ leaves = parse_multiple_values.map do |x|
102
+ leaf = ScopedSearch::QueryLanguage::AST::LeafNode.new(x)
103
+ ScopedSearch::QueryLanguage::AST::OperatorNode.new(cmp, [leaf])
104
+ end
105
+ ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(log, leaves)
84
106
  end
85
107
 
86
108
  # Parses an infix expression, i.e. <field> <operator> <value>
@@ -109,7 +131,7 @@ module ScopedSearch::QueryLanguage::Parser
109
131
  value = []
110
132
  value << current_token if String === next_token until peek_token.nil? || peek_token == :rparen
111
133
  next_token if peek_token == :rparen # consume the :rparen
112
- value.join(',')
134
+ value
113
135
  end
114
136
 
115
137
  # This can either be a constant value or a field name.
@@ -117,7 +139,7 @@ module ScopedSearch::QueryLanguage::Parser
117
139
  if String === peek_token
118
140
  ScopedSearch::QueryLanguage::AST::LeafNode.new(next_token)
119
141
  elsif ([:in, :notin].include? current_token)
120
- value = parse_multiple_values()
142
+ value = parse_multiple_values().join(',')
121
143
  ScopedSearch::QueryLanguage::AST::LeafNode.new(value)
122
144
  else
123
145
  raise ScopedSearch::QueryNotSupported, "Value expected but found #{peek_token.inspect}"
@@ -1,3 +1,3 @@
1
1
  module ScopedSearch
2
- VERSION = "4.1.11"
2
+ VERSION = "4.1.13"
3
3
  end
@@ -102,7 +102,27 @@ describe ScopedSearch::QueryLanguage::Parser do
102
102
  'set? a b null? c'.should parse_to([:and, [:notnull, 'a'], 'b', [:null, 'c']])
103
103
  end
104
104
 
105
+ it 'should parse logical operators with a single argument' do
106
+ '& a'.should parse_to('a')
107
+ '& & & & a & b & & &'.should parse_to([:and, 'a', 'b'])
108
+ end
109
+
110
+ it 'should parse in and not in operators with no lhs' do
111
+ '^ a'.should parse_to([:eq, 'a'])
112
+ '^ a b'.should parse_to([:or, [:eq, 'a'], [:eq, 'b']])
113
+ '^ a,b'.should parse_to([:or, [:eq, 'a'], [:eq, 'b']])
114
+
115
+ '^ (a b)'.should parse_to([:or, [:eq, 'a'], [:eq, 'b']])
116
+ '^ (a,b)'.should parse_to([:or, [:eq, 'a'], [:eq, 'b']])
117
+
118
+ '!^ a'.should parse_to([:ne, 'a'])
119
+ '!^ a b'.should parse_to([:and, [:ne, 'a'], [:ne, 'b']])
120
+ '!^ a,b'.should parse_to([:and, [:ne, 'a'], [:ne, 'b']])
121
+ '!^ (a b)'.should parse_to([:and, [:ne, 'a'], [:ne, 'b']])
122
+ '!^ (a,b)'.should parse_to([:and, [:ne, 'a'], [:ne, 'b']])
123
+ end
124
+
105
125
  it "should refuse to parse an empty not expression" do
106
126
  lambda { ScopedSearch::QueryLanguage::Compiler.parse('!()|*') }.should raise_error(ScopedSearch::QueryNotSupported)
107
- end
127
+ end
108
128
  end
@@ -68,6 +68,8 @@ describe ScopedSearch::QueryBuilder do
68
68
 
69
69
  lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field ^ (1,2)') }.should_not raise_error
70
70
  lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field ^ (1,a)') }.should raise_error(ScopedSearch::QueryNotSupported)
71
+ lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field !^ (1,2)') }.should_not raise_error
72
+ lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field !^ (1,a)') }.should raise_error(ScopedSearch::QueryNotSupported)
71
73
  end
72
74
 
73
75
  it "should display custom error from validator" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scoped_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.11
4
+ version: 4.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amos Benari
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-06-05 00:00:00.000000000 Z
13
+ date: 2024-12-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -165,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  requirements: []
168
- rubygems_version: 3.4.13
168
+ rubygems_version: 3.3.27
169
169
  signing_key:
170
170
  specification_version: 4
171
171
  summary: Easily search you ActiveRecord models with a simple query language using