search_cop 1.0.9 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +50 -0
- data/.rubocop.yml +134 -0
- data/CHANGELOG.md +29 -7
- data/Gemfile +4 -17
- data/README.md +69 -3
- data/Rakefile +0 -1
- data/docker-compose.yml +18 -0
- data/gemfiles/rails5.gemfile +13 -0
- data/gemfiles/rails6.gemfile +13 -0
- data/gemfiles/rails7.gemfile +13 -0
- data/lib/search_cop/grammar_parser.rb +3 -4
- data/lib/search_cop/hash_parser.rb +20 -18
- data/lib/search_cop/helpers.rb +15 -0
- data/lib/search_cop/query_builder.rb +3 -5
- data/lib/search_cop/query_info.rb +0 -2
- data/lib/search_cop/search_scope.rb +2 -4
- data/lib/search_cop/version.rb +1 -1
- data/lib/search_cop/visitors/mysql.rb +4 -2
- data/lib/search_cop/visitors/postgres.rb +5 -3
- data/lib/search_cop/visitors/visitor.rb +7 -5
- data/lib/search_cop/visitors.rb +0 -2
- data/lib/search_cop.rb +15 -13
- data/lib/search_cop_grammar/attributes.rb +53 -34
- data/lib/search_cop_grammar/nodes.rb +0 -2
- data/lib/search_cop_grammar.rb +13 -4
- data/lib/search_cop_grammar.treetop +6 -4
- data/search_cop.gemspec +9 -11
- data/test/and_test.rb +6 -8
- data/test/boolean_test.rb +7 -9
- data/test/database.yml +4 -1
- data/test/date_test.rb +38 -12
- data/test/datetime_test.rb +45 -12
- data/test/default_operator_test.rb +51 -0
- data/test/error_test.rb +2 -4
- data/test/float_test.rb +16 -11
- data/test/fulltext_test.rb +6 -8
- data/test/hash_test.rb +32 -34
- data/test/integer_test.rb +9 -11
- data/test/not_test.rb +6 -8
- data/test/or_test.rb +8 -10
- data/test/scope_test.rb +11 -13
- data/test/search_cop_test.rb +42 -36
- data/test/string_test.rb +67 -19
- data/test/test_helper.rb +28 -18
- data/test/visitor_test.rb +4 -6
- metadata +25 -42
- data/.travis.yml +0 -45
- data/Appraisals +0 -21
- data/gemfiles/3.2.gemfile +0 -25
- data/gemfiles/4.0.gemfile +0 -25
- data/gemfiles/4.1.gemfile +0 -25
- data/gemfiles/4.2.gemfile +0 -25
- data/gemfiles/5.0.gemfile +0 -25
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
1
|
module SearchCop
|
3
2
|
module Visitors
|
4
3
|
module Postgres
|
4
|
+
# rubocop:disable Naming/MethodName
|
5
|
+
|
5
6
|
class FulltextQuery < Visitor
|
6
7
|
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(node)
|
7
8
|
"!'#{node.right.gsub(/[\s&|!:'"]+/, " ")}'"
|
@@ -21,7 +22,7 @@ module SearchCop
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def visit_SearchCopGrammar_Nodes_Matches(node)
|
24
|
-
"(#{visit node.left} IS NOT NULL AND #{visit node.left} ILIKE #{visit node.right})"
|
25
|
+
"(#{visit node.left} IS NOT NULL AND #{visit node.left} ILIKE #{visit node.right} ESCAPE #{visit "\\"})"
|
25
26
|
end
|
26
27
|
|
27
28
|
def visit_SearchCopGrammar_Attributes_Collection(node)
|
@@ -41,7 +42,8 @@ module SearchCop
|
|
41
42
|
|
42
43
|
"to_tsvector(#{visit dictionary}, #{visit node.collection}) @@ to_tsquery(#{visit dictionary}, #{visit FulltextQuery.new(connection).visit(node.node)})"
|
43
44
|
end
|
45
|
+
|
46
|
+
# rubocop:enable Naming/MethodName
|
44
47
|
end
|
45
48
|
end
|
46
49
|
end
|
47
|
-
|
@@ -1,14 +1,15 @@
|
|
1
|
-
|
2
1
|
module SearchCop
|
3
2
|
module Visitors
|
4
3
|
class Visitor
|
4
|
+
# rubocop:disable Naming/MethodName
|
5
|
+
|
5
6
|
attr_accessor :connection
|
6
7
|
|
7
8
|
def initialize(connection)
|
8
9
|
@connection = connection
|
9
10
|
|
10
|
-
extend(SearchCop::Visitors::Mysql) if @connection.
|
11
|
-
extend(SearchCop::Visitors::Postgres) if @connection.
|
11
|
+
extend(SearchCop::Visitors::Mysql) if @connection.adapter_name =~ /mysql/i
|
12
|
+
extend(SearchCop::Visitors::Postgres) if @connection.adapter_name =~ /postgres|postgis/i
|
12
13
|
end
|
13
14
|
|
14
15
|
def visit(visit_node = node)
|
@@ -48,7 +49,7 @@ module SearchCop
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def visit_SearchCopGrammar_Nodes_Matches(node)
|
51
|
-
"(#{visit node.left} IS NOT NULL AND #{visit node.left} LIKE #{visit node.right})"
|
52
|
+
"(#{visit node.left} IS NOT NULL AND #{visit node.left} LIKE #{visit node.right} ESCAPE #{visit "\\"})"
|
52
53
|
end
|
53
54
|
|
54
55
|
def visit_SearchCopGrammar_Nodes_Not(node)
|
@@ -95,7 +96,8 @@ module SearchCop
|
|
95
96
|
alias :visit_Fixnum :quote
|
96
97
|
alias :visit_Symbol :quote
|
97
98
|
alias :visit_Integer :quote
|
99
|
+
|
100
|
+
# rubocop:enable Naming/MethodName
|
98
101
|
end
|
99
102
|
end
|
100
103
|
end
|
101
|
-
|
data/lib/search_cop/visitors.rb
CHANGED
data/lib/search_cop.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
1
|
require "search_cop/version"
|
2
|
+
require "search_cop/helpers"
|
3
3
|
require "search_cop/search_scope"
|
4
4
|
require "search_cop/query_info"
|
5
5
|
require "search_cop/query_builder"
|
@@ -8,21 +8,24 @@ require "search_cop/hash_parser"
|
|
8
8
|
require "search_cop/visitors"
|
9
9
|
|
10
10
|
module SearchCop
|
11
|
-
class
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
class SpecificationError < Error; end
|
12
14
|
class UnknownAttribute < SpecificationError; end
|
15
|
+
class UnknownDefaultOperator < SpecificationError; end
|
13
16
|
|
14
|
-
class RuntimeError <
|
17
|
+
class RuntimeError < Error; end
|
15
18
|
class UnknownColumn < RuntimeError; end
|
16
19
|
class NoSearchableAttributes < RuntimeError; end
|
17
20
|
class IncompatibleDatatype < RuntimeError; end
|
18
21
|
class ParseError < RuntimeError; end
|
19
22
|
|
20
23
|
module Parser
|
21
|
-
def self.parse(query, query_info)
|
24
|
+
def self.parse(query, query_info, query_options = {})
|
22
25
|
if query.is_a?(Hash)
|
23
|
-
SearchCop::HashParser.new(query_info).parse(query)
|
26
|
+
SearchCop::HashParser.new(query_info).parse(query, query_options)
|
24
27
|
else
|
25
|
-
SearchCop::GrammarParser.new(query_info).parse(query)
|
28
|
+
SearchCop::GrammarParser.new(query_info).parse(query, query_options)
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
@@ -41,24 +44,24 @@ module SearchCop
|
|
41
44
|
search_scopes[name] = SearchScope.new(name, self)
|
42
45
|
search_scopes[name].instance_exec(&block)
|
43
46
|
|
44
|
-
|
45
|
-
|
47
|
+
send(:define_singleton_method, name) { |query, query_options = {}| search_cop(query, name, query_options) }
|
48
|
+
send(:define_singleton_method, "unsafe_#{name}") { |query, query_options = {}| unsafe_search_cop(query, name, query_options) }
|
46
49
|
end
|
47
50
|
|
48
51
|
def search_reflection(scope_name)
|
49
52
|
search_scopes[scope_name].reflection
|
50
53
|
end
|
51
54
|
|
52
|
-
def search_cop(query, scope_name)
|
53
|
-
unsafe_search_cop
|
55
|
+
def search_cop(query, scope_name, query_options)
|
56
|
+
unsafe_search_cop(query, scope_name, query_options)
|
54
57
|
rescue SearchCop::RuntimeError
|
55
58
|
respond_to?(:none) ? none : where("1 = 0")
|
56
59
|
end
|
57
60
|
|
58
|
-
def unsafe_search_cop(query, scope_name)
|
61
|
+
def unsafe_search_cop(query, scope_name, query_options)
|
59
62
|
return respond_to?(:scoped) ? scoped : all if query.blank?
|
60
63
|
|
61
|
-
query_builder = QueryBuilder.new(self, query, search_scopes[scope_name])
|
64
|
+
query_builder = QueryBuilder.new(self, query, search_scopes[scope_name], query_options)
|
62
65
|
|
63
66
|
scope = instance_exec(&search_scopes[scope_name].reflection.scope) if search_scopes[scope_name].reflection.scope
|
64
67
|
scope ||= eager_load(query_builder.associations) if query_builder.associations.any?
|
@@ -67,4 +70,3 @@ module SearchCop
|
|
67
70
|
end
|
68
71
|
end
|
69
72
|
end
|
70
|
-
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
require "treetop"
|
3
2
|
|
4
3
|
module SearchCopGrammar
|
@@ -28,9 +27,9 @@ module SearchCopGrammar
|
|
28
27
|
end
|
29
28
|
|
30
29
|
[:eq, :not_eq, :lt, :lteq, :gt, :gteq].each do |method|
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
define_method method do |value|
|
31
|
+
attributes.collect! { |attribute| attribute.send method, value }.inject(:or)
|
32
|
+
end
|
34
33
|
end
|
35
34
|
|
36
35
|
def generator(generator, value)
|
@@ -137,7 +136,7 @@ module SearchCopGrammar
|
|
137
136
|
false
|
138
137
|
end
|
139
138
|
|
140
|
-
{ :
|
139
|
+
{ eq: "Equality", not_eq: "NotEqual", lt: "LessThan", lteq: "LessThanOrEqual", gt: "GreaterThan", gteq: "GreaterThanOrEqual", matches: "Matches" }.each do |method, class_name|
|
141
140
|
define_method method do |value|
|
142
141
|
raise(SearchCop::IncompatibleDatatype, "Incompatible datatype for #{value}") unless compatible?(value)
|
143
142
|
|
@@ -146,19 +145,32 @@ module SearchCopGrammar
|
|
146
145
|
end
|
147
146
|
|
148
147
|
def method_missing(name, *args, &block)
|
149
|
-
@attribute.
|
148
|
+
if @attribute.respond_to?(name)
|
149
|
+
@attribute.send(name, *args, &block)
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
150
153
|
end
|
151
154
|
|
152
|
-
def
|
153
|
-
|
155
|
+
def respond_to_missing?(*args)
|
156
|
+
@attribute.respond_to?(*args) || super
|
154
157
|
end
|
155
158
|
end
|
156
159
|
|
157
160
|
class String < Base
|
158
161
|
def matches_value(value)
|
159
|
-
|
162
|
+
res = value.gsub(/[%_\\]/) { |char| "\\#{char}" }
|
160
163
|
|
161
|
-
|
164
|
+
if value.strip =~ /^\*|\*$/
|
165
|
+
res = res.gsub(/^\*/, "%") if options[:left_wildcard] != false
|
166
|
+
res = res.gsub(/\*$/, "%") if options[:right_wildcard] != false
|
167
|
+
|
168
|
+
return res
|
169
|
+
end
|
170
|
+
|
171
|
+
res = "%#{res}" if options[:left_wildcard] != false
|
172
|
+
res = "#{res}%" if options[:right_wildcard] != false
|
173
|
+
res
|
162
174
|
end
|
163
175
|
|
164
176
|
def matches(value)
|
@@ -176,7 +188,7 @@ module SearchCopGrammar
|
|
176
188
|
|
177
189
|
class Float < WithoutMatches
|
178
190
|
def compatible?(value)
|
179
|
-
return true if value.to_s =~
|
191
|
+
return true if value.to_s =~ /^-?[0-9]+(\.[0-9]+)?$/
|
180
192
|
|
181
193
|
false
|
182
194
|
end
|
@@ -196,20 +208,24 @@ module SearchCopGrammar
|
|
196
208
|
|
197
209
|
class Datetime < WithoutMatches
|
198
210
|
def parse(value)
|
199
|
-
return value
|
200
|
-
|
201
|
-
if value =~ /^[0-9]{
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
elsif value =~ /^
|
206
|
-
::Time.new(
|
207
|
-
elsif value =~
|
211
|
+
return value..value unless value.is_a?(::String)
|
212
|
+
|
213
|
+
if value =~ /^[0-9]+ (hour|day|week|month|year)s{0,1} (ago)$/
|
214
|
+
number, period, ago = value.split(" ")
|
215
|
+
time = number.to_i.send(period.to_sym).send(ago.to_sym)
|
216
|
+
time..::Time.now
|
217
|
+
elsif value =~ /^[0-9]{4}$/
|
218
|
+
::Time.new(value).beginning_of_year..::Time.new(value).end_of_year
|
219
|
+
elsif value =~ %r{^([0-9]{4})(\.|-|/)([0-9]{1,2})$}
|
220
|
+
::Time.new(Regexp.last_match(1), Regexp.last_match(3), 15).beginning_of_month..::Time.new(Regexp.last_match(1), Regexp.last_match(3), 15).end_of_month
|
221
|
+
elsif value =~ %r{^([0-9]{1,2})(\.|-|/)([0-9]{4})$}
|
222
|
+
::Time.new(Regexp.last_match(3), Regexp.last_match(1), 15).beginning_of_month..::Time.new(Regexp.last_match(3), Regexp.last_match(1), 15).end_of_month
|
223
|
+
elsif value =~ %r{^[0-9]{4}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{1,2}$} || value =~ %r{^[0-9]{1,2}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{4}$}
|
208
224
|
time = ::Time.parse(value)
|
209
|
-
time.beginning_of_day
|
210
|
-
elsif value =~
|
225
|
+
time.beginning_of_day..time.end_of_day
|
226
|
+
elsif value =~ %r{[0-9]{4}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{1,2}} || value =~ %r{[0-9]{1,2}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{4}}
|
211
227
|
time = ::Time.parse(value)
|
212
|
-
time
|
228
|
+
time..time
|
213
229
|
else
|
214
230
|
raise ArgumentError
|
215
231
|
end
|
@@ -242,17 +258,21 @@ module SearchCopGrammar
|
|
242
258
|
|
243
259
|
class Date < Datetime
|
244
260
|
def parse(value)
|
245
|
-
return value
|
246
|
-
|
247
|
-
if value =~ /^[0-9]{
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
elsif value =~ /^
|
252
|
-
::Date.new(
|
253
|
-
elsif value =~
|
261
|
+
return value..value unless value.is_a?(::String)
|
262
|
+
|
263
|
+
if value =~ /^[0-9]+ (day|week|month|year)s{0,1} (ago)$/
|
264
|
+
number, period, ago = value.split(" ")
|
265
|
+
time = number.to_i.send(period.to_sym).send(ago.to_sym)
|
266
|
+
time.to_date..::Date.today
|
267
|
+
elsif value =~ /^[0-9]{4}$/
|
268
|
+
::Date.new(value.to_i).beginning_of_year..::Date.new(value.to_i).end_of_year
|
269
|
+
elsif value =~ %r{^([0-9]{4})(\.|-|/)([0-9]{1,2})$}
|
270
|
+
::Date.new(Regexp.last_match(1).to_i, Regexp.last_match(3).to_i, 15).beginning_of_month..::Date.new(Regexp.last_match(1).to_i, Regexp.last_match(3).to_i, 15).end_of_month
|
271
|
+
elsif value =~ %r{^([0-9]{1,2})(\.|-|/)([0-9]{4})$}
|
272
|
+
::Date.new(Regexp.last_match(3).to_i, Regexp.last_match(1).to_i, 15).beginning_of_month..::Date.new(Regexp.last_match(3).to_i, Regexp.last_match(1).to_i, 15).end_of_month
|
273
|
+
elsif value =~ %r{[0-9]{4}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{1,2}} || value =~ %r{[0-9]{1,2}(\.|-|/)[0-9]{1,2}(\.|-|/)[0-9]{4}}
|
254
274
|
date = ::Date.parse(value)
|
255
|
-
date
|
275
|
+
date..date
|
256
276
|
else
|
257
277
|
raise ArgumentError
|
258
278
|
end
|
@@ -273,4 +293,3 @@ module SearchCopGrammar
|
|
273
293
|
end
|
274
294
|
end
|
275
295
|
end
|
276
|
-
|
data/lib/search_cop_grammar.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
|
-
|
2
1
|
require "search_cop_grammar/attributes"
|
3
2
|
require "search_cop_grammar/nodes"
|
4
3
|
|
5
4
|
module SearchCopGrammar
|
6
5
|
class BaseNode < Treetop::Runtime::SyntaxNode
|
7
|
-
|
6
|
+
attr_writer :query_info, :query_options
|
8
7
|
|
9
8
|
def query_info
|
10
9
|
(@query_info ||= nil) || parent.query_info
|
11
10
|
end
|
12
11
|
|
12
|
+
def query_options
|
13
|
+
(@query_options ||= nil) || parent.query_options
|
14
|
+
end
|
15
|
+
|
13
16
|
def evaluate
|
14
17
|
elements.collect(&:evaluate).inject(:and)
|
15
18
|
end
|
16
19
|
|
17
20
|
def elements
|
18
|
-
super.
|
21
|
+
super.reject { |element| element.instance_of?(Treetop::Runtime::SyntaxNode) }
|
19
22
|
end
|
20
23
|
|
21
24
|
def collection_for(key)
|
@@ -110,6 +113,13 @@ module SearchCopGrammar
|
|
110
113
|
end
|
111
114
|
end
|
112
115
|
|
116
|
+
class AndOrExpression < BaseNode
|
117
|
+
def evaluate
|
118
|
+
default_operator = SearchCop::Helpers.sanitize_default_operator(query_options)
|
119
|
+
[elements.first.evaluate, elements.last.evaluate].inject(default_operator)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
113
123
|
class OrExpression < BaseNode
|
114
124
|
def evaluate
|
115
125
|
[elements.first.evaluate, elements.last.evaluate].inject(:or)
|
@@ -142,4 +152,3 @@ module SearchCopGrammar
|
|
142
152
|
|
143
153
|
class Value < BaseNode; end
|
144
154
|
end
|
145
|
-
|
@@ -9,7 +9,9 @@ grammar SearchCopGrammar
|
|
9
9
|
end
|
10
10
|
|
11
11
|
rule and_expression
|
12
|
-
or_expression
|
12
|
+
or_expression space? ('AND' / 'and') space? complex_expression <AndExpression> /
|
13
|
+
or_expression space !('OR' / 'or') complex_expression <AndOrExpression> /
|
14
|
+
or_expression
|
13
15
|
end
|
14
16
|
|
15
17
|
rule or_expression
|
@@ -37,7 +39,7 @@ grammar SearchCopGrammar
|
|
37
39
|
end
|
38
40
|
|
39
41
|
rule anywhere_expression
|
40
|
-
"'" [^\']* "'" <SingleQuotedAnywhereExpression> / '"' [^\"]* '"' <DoubleQuotedAnywhereExpression> / [
|
42
|
+
"'" [^\']* "'" <SingleQuotedAnywhereExpression> / '"' [^\"]* '"' <DoubleQuotedAnywhereExpression> / [^[:blank:]()]+ <AnywhereExpression>
|
41
43
|
end
|
42
44
|
|
43
45
|
rule simple_column
|
@@ -45,11 +47,11 @@ grammar SearchCopGrammar
|
|
45
47
|
end
|
46
48
|
|
47
49
|
rule value
|
48
|
-
"'" [^\']* "'" <SingleQuotedValue> / '"' [^\"]* '"' <DoubleQuotedValue> / [
|
50
|
+
"'" [^\']* "'" <SingleQuotedValue> / '"' [^\"]* '"' <DoubleQuotedValue> / [^[:blank:]()]+ <Value>
|
49
51
|
end
|
50
52
|
|
51
53
|
rule space
|
52
|
-
[
|
54
|
+
[[:blank:]]+
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
data/search_cop.gemspec
CHANGED
@@ -1,29 +1,27 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "search_cop/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "search_cop"
|
8
7
|
spec.version = SearchCop::VERSION
|
9
8
|
spec.authors = ["Benjamin Vetter"]
|
10
9
|
spec.email = ["vetter@flakks.com"]
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
10
|
+
spec.description = "Search engine like fulltext query support for ActiveRecord"
|
11
|
+
spec.summary = "Easily perform complex search engine like fulltext queries on your ActiveRecord models"
|
13
12
|
spec.homepage = "https://github.com/mrkamel/search_cop"
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
16
|
-
spec.files = `git ls-files`.split(
|
15
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
17
|
spec.require_paths = ["lib"]
|
20
18
|
|
21
19
|
spec.add_dependency "treetop"
|
22
20
|
|
23
|
-
spec.add_development_dependency "bundler"
|
24
|
-
spec.add_development_dependency "rake"
|
25
21
|
spec.add_development_dependency "activerecord", ">= 3.0.0"
|
26
|
-
spec.add_development_dependency "
|
27
|
-
spec.add_development_dependency "
|
22
|
+
spec.add_development_dependency "bundler"
|
23
|
+
spec.add_development_dependency "factory_bot"
|
28
24
|
spec.add_development_dependency "minitest"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rubocop"
|
29
27
|
end
|
data/test/and_test.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
|
2
|
-
require File.expand_path("../test_helper", __FILE__)
|
1
|
+
require File.expand_path("test_helper", __dir__)
|
3
2
|
|
4
3
|
class AndTest < SearchCop::TestCase
|
5
4
|
def test_and_string
|
6
|
-
expected = create(:product, :
|
7
|
-
rejected = create(:product, :
|
5
|
+
expected = create(:product, title: "expected title", description: "Description")
|
6
|
+
rejected = create(:product, title: "Rejected title", description: "Description")
|
8
7
|
|
9
8
|
results = Product.search("title: 'Expected title' description: Description")
|
10
9
|
|
@@ -15,13 +14,12 @@ class AndTest < SearchCop::TestCase
|
|
15
14
|
end
|
16
15
|
|
17
16
|
def test_and_hash
|
18
|
-
expected = create(:product, :
|
19
|
-
rejected = create(:product, :
|
17
|
+
expected = create(:product, title: "Expected title", description: "Description")
|
18
|
+
rejected = create(:product, title: "Rejected title", description: "Description")
|
20
19
|
|
21
|
-
results = Product.search(:
|
20
|
+
results = Product.search(and: [{ title: "Expected title" }, { description: "Description" }])
|
22
21
|
|
23
22
|
assert_includes results, expected
|
24
23
|
refute_includes results, rejected
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
data/test/boolean_test.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
require File.expand_path("../test_helper", __FILE__)
|
1
|
+
require File.expand_path("test_helper", __dir__)
|
3
2
|
|
4
3
|
class BooleanTest < SearchCop::TestCase
|
5
4
|
def test_mapping
|
6
|
-
product = create(:product, :
|
5
|
+
product = create(:product, available: true)
|
7
6
|
|
8
7
|
assert_includes Product.search("available: 1"), product
|
9
8
|
assert_includes Product.search("available: true"), product
|
10
9
|
assert_includes Product.search("available: yes"), product
|
11
10
|
|
12
|
-
product = create(:product, :
|
11
|
+
product = create(:product, available: false)
|
13
12
|
|
14
13
|
assert_includes Product.search("available: 0"), product
|
15
14
|
assert_includes Product.search("available: false"), product
|
@@ -17,28 +16,28 @@ class BooleanTest < SearchCop::TestCase
|
|
17
16
|
end
|
18
17
|
|
19
18
|
def test_anywhere
|
20
|
-
product = create(:product, :
|
19
|
+
product = create(:product, available: true)
|
21
20
|
|
22
21
|
assert_includes Product.search("true"), product
|
23
22
|
refute_includes Product.search("false"), product
|
24
23
|
end
|
25
24
|
|
26
25
|
def test_includes
|
27
|
-
product = create(:product, :
|
26
|
+
product = create(:product, available: true)
|
28
27
|
|
29
28
|
assert_includes Product.search("available: true"), product
|
30
29
|
refute_includes Product.search("available: false"), product
|
31
30
|
end
|
32
31
|
|
33
32
|
def test_equals
|
34
|
-
product = create(:product, :
|
33
|
+
product = create(:product, available: true)
|
35
34
|
|
36
35
|
assert_includes Product.search("available = true"), product
|
37
36
|
refute_includes Product.search("available = false"), product
|
38
37
|
end
|
39
38
|
|
40
39
|
def test_equals_not
|
41
|
-
product = create(:product, :
|
40
|
+
product = create(:product, available: false)
|
42
41
|
|
43
42
|
assert_includes Product.search("available != true"), product
|
44
43
|
refute_includes Product.search("available != false"), product
|
@@ -50,4 +49,3 @@ class BooleanTest < SearchCop::TestCase
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
end
|
53
|
-
|
data/test/database.yml
CHANGED
@@ -6,12 +6,15 @@ sqlite:
|
|
6
6
|
mysql:
|
7
7
|
adapter: mysql2
|
8
8
|
database: search_cop
|
9
|
+
host: 127.0.0.1
|
9
10
|
username: root
|
10
11
|
encoding: utf8
|
11
12
|
|
12
13
|
postgres:
|
14
|
+
host: 127.0.0.1
|
13
15
|
adapter: postgresql
|
14
16
|
database: search_cop
|
15
|
-
username:
|
17
|
+
username: search_cop
|
18
|
+
password: secret
|
16
19
|
encoding: utf8
|
17
20
|
|
data/test/date_test.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
|
-
require File.expand_path("../test_helper", __FILE__)
|
1
|
+
require File.expand_path("test_helper", __dir__)
|
3
2
|
|
4
3
|
class DateTest < SearchCop::TestCase
|
5
4
|
def test_mapping
|
6
|
-
product = create(:product, :
|
5
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
7
6
|
|
8
7
|
assert_includes Product.search("created_on: 2014"), product
|
9
8
|
assert_includes Product.search("created_on: 2014-05"), product
|
@@ -11,61 +10,89 @@ class DateTest < SearchCop::TestCase
|
|
11
10
|
end
|
12
11
|
|
13
12
|
def test_anywhere
|
14
|
-
product = create(:product, :
|
13
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
15
14
|
|
16
15
|
assert_includes Product.search("2014-05-01"), product
|
17
16
|
refute_includes Product.search("2014-05-02"), product
|
18
17
|
end
|
19
18
|
|
20
19
|
def test_includes
|
21
|
-
product = create(:product, :
|
20
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
22
21
|
|
23
22
|
assert_includes Product.search("created_on: 2014-05-01"), product
|
24
23
|
refute_includes Product.search("created_on: 2014-05-02"), product
|
25
24
|
end
|
26
25
|
|
27
26
|
def test_equals
|
28
|
-
product = create(:product, :
|
27
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
29
28
|
|
30
29
|
assert_includes Product.search("created_on = 2014-05-01"), product
|
31
30
|
refute_includes Product.search("created_on = 2014-05-02"), product
|
32
31
|
end
|
33
32
|
|
34
33
|
def test_equals_not
|
35
|
-
product = create(:product, :
|
34
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
36
35
|
|
37
36
|
assert_includes Product.search("created_on != 2014-05-02"), product
|
38
37
|
refute_includes Product.search("created_on != 2014-05-01"), product
|
39
38
|
end
|
40
39
|
|
41
40
|
def test_greater
|
42
|
-
product = create(:product, :
|
41
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
43
42
|
|
44
43
|
assert_includes Product.search("created_on > 2014-04-01"), product
|
45
44
|
refute_includes Product.search("created_on > 2014-05-01"), product
|
46
45
|
end
|
47
46
|
|
48
47
|
def test_greater_equals
|
49
|
-
product = create(:product, :
|
48
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
50
49
|
|
51
50
|
assert_includes Product.search("created_on >= 2014-05-01"), product
|
52
51
|
refute_includes Product.search("created_on >= 2014-05-02"), product
|
53
52
|
end
|
54
53
|
|
55
54
|
def test_less
|
56
|
-
product = create(:product, :
|
55
|
+
product = create(:product, created_on: Date.parse("2014-05-01"))
|
57
56
|
|
58
57
|
assert_includes Product.search("created_on < 2014-05-02"), product
|
59
58
|
refute_includes Product.search("created_on < 2014-05-01"), product
|
60
59
|
end
|
61
60
|
|
62
61
|
def test_less_equals
|
63
|
-
product = create(:product, :
|
62
|
+
product = create(:product, created_on: Date.parse("2014-05-02"))
|
64
63
|
|
65
64
|
assert_includes Product.search("created_on <= 2014-05-02"), product
|
66
65
|
refute_includes Product.search("created_on <= 2014-05-01"), product
|
67
66
|
end
|
68
67
|
|
68
|
+
def test_days_ago
|
69
|
+
product = create(:product, created_at: 2.days.ago.to_date)
|
70
|
+
|
71
|
+
assert_includes Product.search("created_at <= '1 day ago'"), product
|
72
|
+
refute_includes Product.search("created_at <= '3 days ago'"), product
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_weeks_ago
|
76
|
+
product = create(:product, created_at: 2.weeks.ago.to_date)
|
77
|
+
|
78
|
+
assert_includes Product.search("created_at <= '1 weeks ago'"), product
|
79
|
+
refute_includes Product.search("created_at <= '3 weeks ago'"), product
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_months_ago
|
83
|
+
product = create(:product, created_at: 2.months.ago.to_date)
|
84
|
+
|
85
|
+
assert_includes Product.search("created_at <= '1 months ago'"), product
|
86
|
+
refute_includes Product.search("created_at <= '3 months ago'"), product
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_years_ago
|
90
|
+
product = create(:product, created_at: 2.years.ago.to_date)
|
91
|
+
|
92
|
+
assert_includes Product.search("created_at <= '1 years ago'"), product
|
93
|
+
refute_includes Product.search("created_at <= '3 years ago'"), product
|
94
|
+
end
|
95
|
+
|
69
96
|
def test_no_overflow
|
70
97
|
assert_nothing_raised do
|
71
98
|
Product.search("created_on: 1000000").to_a
|
@@ -78,4 +105,3 @@ class DateTest < SearchCop::TestCase
|
|
78
105
|
end
|
79
106
|
end
|
80
107
|
end
|
81
|
-
|