search_cop 1.0.0
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.
- data/.gitignore +18 -0
- data/.travis.yml +33 -0
- data/Appraisals +14 -0
- data/Gemfile +23 -0
- data/LICENSE.txt +22 -0
- data/MIGRATION.md +66 -0
- data/README.md +530 -0
- data/Rakefile +9 -0
- data/gemfiles/3.2.gemfile +26 -0
- data/gemfiles/4.0.gemfile +26 -0
- data/gemfiles/4.1.gemfile +26 -0
- data/lib/search_cop/arel/visitors.rb +223 -0
- data/lib/search_cop/arel.rb +4 -0
- data/lib/search_cop/grammar_parser.rb +22 -0
- data/lib/search_cop/hash_parser.rb +42 -0
- data/lib/search_cop/query_builder.rb +26 -0
- data/lib/search_cop/query_info.rb +13 -0
- data/lib/search_cop/search_scope.rb +62 -0
- data/lib/search_cop/version.rb +3 -0
- data/lib/search_cop.rb +71 -0
- data/lib/search_cop_grammar/attributes.rb +229 -0
- data/lib/search_cop_grammar/nodes.rb +183 -0
- data/lib/search_cop_grammar.rb +133 -0
- data/lib/search_cop_grammar.treetop +55 -0
- data/search_cop.gemspec +29 -0
- data/test/and_test.rb +27 -0
- data/test/boolean_test.rb +53 -0
- data/test/database.yml +17 -0
- data/test/date_test.rb +75 -0
- data/test/datetime_test.rb +76 -0
- data/test/error_test.rb +17 -0
- data/test/float_test.rb +67 -0
- data/test/fulltext_test.rb +27 -0
- data/test/hash_test.rb +97 -0
- data/test/integer_test.rb +67 -0
- data/test/not_test.rb +27 -0
- data/test/or_test.rb +29 -0
- data/test/scope_test.rb +35 -0
- data/test/search_cop_test.rb +131 -0
- data/test/string_test.rb +84 -0
- data/test/test_helper.rb +150 -0
- metadata +216 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
|
2
|
+
require "treetop"
|
3
|
+
|
4
|
+
module SearchCopGrammar
|
5
|
+
module Nodes
|
6
|
+
module Base
|
7
|
+
def and(node)
|
8
|
+
And.new self, node
|
9
|
+
end
|
10
|
+
|
11
|
+
def or(node)
|
12
|
+
Or.new self, node
|
13
|
+
end
|
14
|
+
|
15
|
+
def not
|
16
|
+
Not.new self
|
17
|
+
end
|
18
|
+
|
19
|
+
def can_flatten?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def flatten!
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def can_group?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def group!
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def fulltext?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def can_optimize?
|
40
|
+
can_flatten? || can_group?
|
41
|
+
end
|
42
|
+
|
43
|
+
def optimize!
|
44
|
+
flatten!.group! while can_optimize?
|
45
|
+
|
46
|
+
finalize!
|
47
|
+
end
|
48
|
+
|
49
|
+
def finalize!
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def nodes
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Binary
|
59
|
+
include Base
|
60
|
+
|
61
|
+
attr_accessor :left, :right
|
62
|
+
|
63
|
+
def initialize(left, right)
|
64
|
+
@left = left
|
65
|
+
@right = right
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Equality < Binary; end
|
70
|
+
class NotEqual < Binary; end
|
71
|
+
class GreaterThan < Binary; end
|
72
|
+
class GreaterThanOrEqual < Binary; end
|
73
|
+
class LessThan < Binary; end
|
74
|
+
class LessThanOrEqual < Binary; end
|
75
|
+
class Matches < Binary; end
|
76
|
+
|
77
|
+
class Not
|
78
|
+
include Base
|
79
|
+
|
80
|
+
attr_accessor :object
|
81
|
+
|
82
|
+
def initialize(object)
|
83
|
+
@object = object
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class MatchesFulltext < Binary
|
88
|
+
include Base
|
89
|
+
|
90
|
+
def not
|
91
|
+
MatchesFulltextNot.new left, right
|
92
|
+
end
|
93
|
+
|
94
|
+
def fulltext?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def finalize!
|
99
|
+
FulltextExpression.new collection, self
|
100
|
+
end
|
101
|
+
|
102
|
+
def collection
|
103
|
+
left
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class MatchesFulltextNot < MatchesFulltext; end
|
108
|
+
|
109
|
+
class FulltextExpression
|
110
|
+
include Base
|
111
|
+
|
112
|
+
attr_reader :collection, :node
|
113
|
+
|
114
|
+
def initialize(collection, node)
|
115
|
+
@collection = collection
|
116
|
+
@node = node
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Collection
|
121
|
+
include Base
|
122
|
+
|
123
|
+
attr_reader :nodes
|
124
|
+
|
125
|
+
def initialize(*nodes)
|
126
|
+
@nodes = nodes.flatten
|
127
|
+
end
|
128
|
+
|
129
|
+
def can_flatten?
|
130
|
+
nodes.any?(&:can_flatten?) || nodes.any? { |node| node.is_a?(self.class) || node.nodes.size == 1 }
|
131
|
+
end
|
132
|
+
|
133
|
+
def flatten!(&block)
|
134
|
+
@nodes = nodes.collect(&:flatten!).collect { |node| node.is_a?(self.class) || node.nodes.size == 1 ? node.nodes : node }.flatten
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def can_group?
|
140
|
+
nodes.reject(&:fulltext?).any?(&:can_group?) || nodes.select(&:fulltext?).group_by(&:collection).any? { |_, group| group.size > 1 }
|
141
|
+
end
|
142
|
+
|
143
|
+
def group!
|
144
|
+
@nodes = nodes.reject(&:fulltext?).collect(&:group!) + nodes.select(&:fulltext?).group_by(&:collection).collect { |collection, group| group.size > 1 ? self.class::Fulltext.new(collection, group) : group.first }
|
145
|
+
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
def finalize!
|
150
|
+
@nodes = nodes.collect(&:finalize!)
|
151
|
+
|
152
|
+
self
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class FulltextCollection < Collection
|
157
|
+
attr_reader :collection
|
158
|
+
|
159
|
+
def initialize(collection, *nodes)
|
160
|
+
@collection = collection
|
161
|
+
|
162
|
+
super *nodes
|
163
|
+
end
|
164
|
+
|
165
|
+
def fulltext?
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
def finalize!
|
170
|
+
FulltextExpression.new collection, self
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class And < Collection
|
175
|
+
class Fulltext < FulltextCollection; end
|
176
|
+
end
|
177
|
+
|
178
|
+
class Or < Collection
|
179
|
+
class Fulltext < FulltextCollection; end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
|
2
|
+
require "search_cop_grammar/attributes"
|
3
|
+
require "search_cop_grammar/nodes"
|
4
|
+
|
5
|
+
module SearchCopGrammar
|
6
|
+
class BaseNode < Treetop::Runtime::SyntaxNode
|
7
|
+
attr_accessor :query_info
|
8
|
+
|
9
|
+
def query_info
|
10
|
+
@query_info || parent.query_info
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate
|
14
|
+
elements.collect(&:evaluate).inject(:and)
|
15
|
+
end
|
16
|
+
|
17
|
+
def elements
|
18
|
+
super.select { |element| element.class != Treetop::Runtime::SyntaxNode }
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection_for(key)
|
22
|
+
raise(SearchCop::UnknownColumn, "Unknown column #{key}") if query_info.scope.reflection.attributes[key].nil?
|
23
|
+
|
24
|
+
Attributes::Collection.new query_info, key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OperatorNode < Treetop::Runtime::SyntaxNode
|
29
|
+
def evaluate
|
30
|
+
text_value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ComplexExpression < BaseNode; end
|
35
|
+
class ParenthesesExpression < BaseNode; end
|
36
|
+
|
37
|
+
class ComparativeExpression < BaseNode
|
38
|
+
def evaluate
|
39
|
+
elements[0].collection.send elements[1].method_name, elements[2].text_value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class IncludesOperator < OperatorNode
|
44
|
+
def method_name
|
45
|
+
:matches
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class EqualOperator < OperatorNode
|
50
|
+
def method_name
|
51
|
+
:eq
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class UnequalOperator < OperatorNode
|
56
|
+
def method_name
|
57
|
+
:not_eq
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class GreaterEqualOperator < OperatorNode
|
62
|
+
def method_name
|
63
|
+
:gteq
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class GreaterOperator < OperatorNode
|
68
|
+
def method_name
|
69
|
+
:gt
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class LessEqualOperator < OperatorNode
|
74
|
+
def method_name
|
75
|
+
:lteq
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class LessOperator < OperatorNode
|
80
|
+
def method_name
|
81
|
+
:lt
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class AnywhereExpression < BaseNode
|
86
|
+
def evaluate
|
87
|
+
queries = query_info.scope.reflection.default_attributes.keys.collect { |key| collection_for key }.select { |collection| collection.compatible? text_value }.collect { |collection| collection.matches text_value }
|
88
|
+
|
89
|
+
raise SearchCop::NoSearchableAttributes if queries.empty?
|
90
|
+
|
91
|
+
queries.flatten.inject(:or)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class AndExpression < BaseNode
|
96
|
+
def evaluate
|
97
|
+
[elements.first.evaluate, elements.last.evaluate].inject(:and)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class OrExpression < BaseNode
|
102
|
+
def evaluate
|
103
|
+
[elements.first.evaluate, elements.last.evaluate].inject(:or)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class NotExpression < BaseNode
|
108
|
+
def evaluate
|
109
|
+
elements.first.evaluate.not
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Column < BaseNode
|
114
|
+
def collection
|
115
|
+
collection_for text_value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class SingleQuotedValue < BaseNode
|
120
|
+
def text_value
|
121
|
+
super.gsub /^'|'$/, ""
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class DoubleQuotedValue < BaseNode
|
126
|
+
def text_value
|
127
|
+
super.gsub /^"|"$/, ""
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Value < BaseNode; end
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
grammar SearchCopGrammar
|
3
|
+
rule complex_expression
|
4
|
+
space? (boolean_expression / expression) space? <ComplexExpression>
|
5
|
+
end
|
6
|
+
|
7
|
+
rule boolean_expression
|
8
|
+
and_expression
|
9
|
+
end
|
10
|
+
|
11
|
+
rule and_expression
|
12
|
+
or_expression (space? ('AND' / 'and') space? / space !('OR' / 'or')) complex_expression <AndExpression> / or_expression
|
13
|
+
end
|
14
|
+
|
15
|
+
rule or_expression
|
16
|
+
expression space? ('OR' / 'or') space? (or_expression / expression) <OrExpression> / expression
|
17
|
+
end
|
18
|
+
|
19
|
+
rule expression
|
20
|
+
parentheses_expression / not_expression / comparative_expression / anywhere_expression
|
21
|
+
end
|
22
|
+
|
23
|
+
rule parentheses_expression
|
24
|
+
'(' complex_expression ')' <ParenthesesExpression>
|
25
|
+
end
|
26
|
+
|
27
|
+
rule not_expression
|
28
|
+
('NOT' space / 'not' space / '-') (comparative_expression / anywhere_expression) <NotExpression>
|
29
|
+
end
|
30
|
+
|
31
|
+
rule comparative_expression
|
32
|
+
simple_column space? comparison_operator space? value <ComparativeExpression>
|
33
|
+
end
|
34
|
+
|
35
|
+
rule comparison_operator
|
36
|
+
':' <IncludesOperator> / '=' <EqualOperator> / '!=' <UnequalOperator> / '>=' <GreaterEqualOperator> / '>' <GreaterOperator> / '<=' <LessEqualOperator> / '<' <LessOperator>
|
37
|
+
end
|
38
|
+
|
39
|
+
rule anywhere_expression
|
40
|
+
("'" ([^\']* <AnywhereExpression>) "'") / ('"' ([^\"]* <AnywhereExpression>) '"') / [^\s()]+ <AnywhereExpression>
|
41
|
+
end
|
42
|
+
|
43
|
+
rule simple_column
|
44
|
+
[a-zA-Z0-9_.]+ <Column>
|
45
|
+
end
|
46
|
+
|
47
|
+
rule value
|
48
|
+
"'" [^\']* "'" <SingleQuotedValue> / '"' [^\"]* '"' <DoubleQuotedValue> / [^\s()]+ <Value>
|
49
|
+
end
|
50
|
+
|
51
|
+
rule space
|
52
|
+
[\s]+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/search_cop.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'search_cop/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "search_cop"
|
8
|
+
spec.version = SearchCop::VERSION
|
9
|
+
spec.authors = ["Benjamin Vetter"]
|
10
|
+
spec.email = ["vetter@flakks.com"]
|
11
|
+
spec.description = %q{Search engine like fulltext query support for ActiveRecord}
|
12
|
+
spec.summary = %q{Easily perform complex search engine like fulltext queries on your ActiveRecord models}
|
13
|
+
spec.homepage = "https://github.com/mrkamel/search_cop"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "treetop"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "activerecord", ">= 3.0.0"
|
26
|
+
spec.add_development_dependency "factory_girl"
|
27
|
+
spec.add_development_dependency "appraisal"
|
28
|
+
spec.add_development_dependency "minitest"
|
29
|
+
end
|
data/test/and_test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class AndTest < SearchCop::TestCase
|
5
|
+
def test_and_string
|
6
|
+
expected = create(:product, :title => "Expected title", :description => "Description")
|
7
|
+
rejected = create(:product, :title => "Rejected title", :description => "Description")
|
8
|
+
|
9
|
+
results = Product.search("title: 'Expected title' description: Description")
|
10
|
+
|
11
|
+
assert_includes results, expected
|
12
|
+
refute_includes results, rejected
|
13
|
+
|
14
|
+
assert_equal results, Product.search("title: 'Expected title' AND description: Description")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_and_hash
|
18
|
+
expected = create(:product, :title => "Expected title", :description => "Description")
|
19
|
+
rejected = create(:product, :title => "Rejected title", :description => "Description")
|
20
|
+
|
21
|
+
results = Product.search(:and => [{:title => "Expected title"}, {:description => "Description"}])
|
22
|
+
|
23
|
+
assert_includes results, expected
|
24
|
+
refute_includes results, rejected
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class BooleanTest < SearchCop::TestCase
|
5
|
+
def test_mapping
|
6
|
+
product = create(:product, :available => true)
|
7
|
+
|
8
|
+
assert_includes Product.search("available: 1"), product
|
9
|
+
assert_includes Product.search("available: true"), product
|
10
|
+
assert_includes Product.search("available: yes"), product
|
11
|
+
|
12
|
+
product = create(:product, :available => false)
|
13
|
+
|
14
|
+
assert_includes Product.search("available: 0"), product
|
15
|
+
assert_includes Product.search("available: false"), product
|
16
|
+
assert_includes Product.search("available: no"), product
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_anywhere
|
20
|
+
product = create(:product, :available => true)
|
21
|
+
|
22
|
+
assert_includes Product.search("true"), product
|
23
|
+
refute_includes Product.search("false"), product
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_includes
|
27
|
+
product = create(:product, :available => true)
|
28
|
+
|
29
|
+
assert_includes Product.search("available: true"), product
|
30
|
+
refute_includes Product.search("available: false"), product
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_equals
|
34
|
+
product = create(:product, :available => true)
|
35
|
+
|
36
|
+
assert_includes Product.search("available = true"), product
|
37
|
+
refute_includes Product.search("available = false"), product
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_equals_not
|
41
|
+
product = create(:product, :available => false)
|
42
|
+
|
43
|
+
assert_includes Product.search("available != true"), product
|
44
|
+
refute_includes Product.search("available != false"), product
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_incompatible_datatype
|
48
|
+
assert_raises SearchCop::IncompatibleDatatype do
|
49
|
+
Product.unsafe_search "available: Value"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/test/database.yml
ADDED
data/test/date_test.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class DateTest < SearchCop::TestCase
|
5
|
+
def test_mapping
|
6
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
7
|
+
|
8
|
+
assert_includes Product.search("created_on: 2014"), product
|
9
|
+
assert_includes Product.search("created_on: 2014-05"), product
|
10
|
+
assert_includes Product.search("created_on: 2014-05-01"), product
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_anywhere
|
14
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
15
|
+
|
16
|
+
assert_includes Product.search("2014-05-01"), product
|
17
|
+
refute_includes Product.search("2014-05-02"), product
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_includes
|
21
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
22
|
+
|
23
|
+
assert_includes Product.search("created_on: 2014-05-01"), product
|
24
|
+
refute_includes Product.search("created_on: 2014-05-02"), product
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_equals
|
28
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
29
|
+
|
30
|
+
assert_includes Product.search("created_on = 2014-05-01"), product
|
31
|
+
refute_includes Product.search("created_on = 2014-05-02"), product
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_equals_not
|
35
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
36
|
+
|
37
|
+
assert_includes Product.search("created_on != 2014-05-02"), product
|
38
|
+
refute_includes Product.search("created_on != 2014-05-01"), product
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_greater
|
42
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
43
|
+
|
44
|
+
assert_includes Product.search("created_on > 2014-04-01"), product
|
45
|
+
refute_includes Product.search("created_on > 2014-05-01"), product
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_greater_equals
|
49
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
50
|
+
|
51
|
+
assert_includes Product.search("created_on >= 2014-05-01"), product
|
52
|
+
refute_includes Product.search("created_on >= 2014-05-02"), product
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_less
|
56
|
+
product = create(:product, :created_on => Date.parse("2014-05-01"))
|
57
|
+
|
58
|
+
assert_includes Product.search("created_on < 2014-05-02"), product
|
59
|
+
refute_includes Product.search("created_on < 2014-05-01"), product
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_less_equals
|
63
|
+
product = create(:product, :created_on => Date.parse("2014-05-02"))
|
64
|
+
|
65
|
+
assert_includes Product.search("created_on <= 2014-05-02"), product
|
66
|
+
refute_includes Product.search("created_on <= 2014-05-01"), product
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_incompatible_datatype
|
70
|
+
assert_raises SearchCop::IncompatibleDatatype do
|
71
|
+
Product.unsafe_search "created_on: Value"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class DatetimeTest < SearchCop::TestCase
|
5
|
+
def test_mapping
|
6
|
+
product = create(:product, :created_at => Time.parse("2014-05-01 12:30:30"))
|
7
|
+
|
8
|
+
assert_includes Product.search("created_at: 2014"), product
|
9
|
+
assert_includes Product.search("created_at: 2014-05"), product
|
10
|
+
assert_includes Product.search("created_at: 2014-05-01"), product
|
11
|
+
assert_includes Product.search("created_at: '2014-05-01 12:30:30'"), product
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_anywhere
|
15
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
16
|
+
|
17
|
+
assert_includes Product.search("2014-05-01"), product
|
18
|
+
refute_includes Product.search("2014-05-02"), product
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_includes
|
22
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
23
|
+
|
24
|
+
assert_includes Product.search("created_at: 2014-05-01"), product
|
25
|
+
refute_includes Product.search("created_at: 2014-05-02"), product
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_equals
|
29
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
30
|
+
|
31
|
+
assert_includes Product.search("created_at = 2014-05-01"), product
|
32
|
+
refute_includes Product.search("created_at = 2014-05-02"), product
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_equals_not
|
36
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
37
|
+
|
38
|
+
assert_includes Product.search("created_at != 2014-05-02"), product
|
39
|
+
refute_includes Product.search("created_at != 2014-05-01"), product
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_greater
|
43
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
44
|
+
|
45
|
+
assert_includes Product.search("created_at > 2014-04-01"), product
|
46
|
+
refute_includes Product.search("created_at > 2014-05-01"), product
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_greater_equals
|
50
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
51
|
+
|
52
|
+
assert_includes Product.search("created_at >= 2014-05-01"), product
|
53
|
+
refute_includes Product.search("created_at >= 2014-05-02"), product
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_less
|
57
|
+
product = create(:product, :created_at => Time.parse("2014-05-01"))
|
58
|
+
|
59
|
+
assert_includes Product.search("created_at < 2014-05-02"), product
|
60
|
+
refute_includes Product.search("created_at < 2014-05-01"), product
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_less_equals
|
64
|
+
product = create(:product, :created_at => Time.parse("2014-05-02"))
|
65
|
+
|
66
|
+
assert_includes Product.search("created_at <= 2014-05-02"), product
|
67
|
+
refute_includes Product.search("created_at <= 2014-05-01"), product
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_incompatible_datatype
|
71
|
+
assert_raises SearchCop::IncompatibleDatatype do
|
72
|
+
Product.unsafe_search "created_at: Value"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class ErrorTest < SearchCop::TestCase
|
5
|
+
def test_parse_error
|
6
|
+
assert_raises SearchCop::ParseError do
|
7
|
+
Product.unsafe_search :title => { :unknown_operator => "Value" }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_unknown_column
|
12
|
+
assert_raises SearchCop::UnknownColumn do
|
13
|
+
Product.unsafe_search "Unknown: Column"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|