search_cop 1.0.9 → 1.2.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 +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
|
-
|