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.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +50 -0
  3. data/.rubocop.yml +134 -0
  4. data/CHANGELOG.md +29 -7
  5. data/Gemfile +4 -17
  6. data/README.md +69 -3
  7. data/Rakefile +0 -1
  8. data/docker-compose.yml +18 -0
  9. data/gemfiles/rails5.gemfile +13 -0
  10. data/gemfiles/rails6.gemfile +13 -0
  11. data/gemfiles/rails7.gemfile +13 -0
  12. data/lib/search_cop/grammar_parser.rb +3 -4
  13. data/lib/search_cop/hash_parser.rb +20 -18
  14. data/lib/search_cop/helpers.rb +15 -0
  15. data/lib/search_cop/query_builder.rb +3 -5
  16. data/lib/search_cop/query_info.rb +0 -2
  17. data/lib/search_cop/search_scope.rb +2 -4
  18. data/lib/search_cop/version.rb +1 -1
  19. data/lib/search_cop/visitors/mysql.rb +4 -2
  20. data/lib/search_cop/visitors/postgres.rb +5 -3
  21. data/lib/search_cop/visitors/visitor.rb +7 -5
  22. data/lib/search_cop/visitors.rb +0 -2
  23. data/lib/search_cop.rb +15 -13
  24. data/lib/search_cop_grammar/attributes.rb +53 -34
  25. data/lib/search_cop_grammar/nodes.rb +0 -2
  26. data/lib/search_cop_grammar.rb +13 -4
  27. data/lib/search_cop_grammar.treetop +6 -4
  28. data/search_cop.gemspec +9 -11
  29. data/test/and_test.rb +6 -8
  30. data/test/boolean_test.rb +7 -9
  31. data/test/database.yml +4 -1
  32. data/test/date_test.rb +38 -12
  33. data/test/datetime_test.rb +45 -12
  34. data/test/default_operator_test.rb +51 -0
  35. data/test/error_test.rb +2 -4
  36. data/test/float_test.rb +16 -11
  37. data/test/fulltext_test.rb +6 -8
  38. data/test/hash_test.rb +32 -34
  39. data/test/integer_test.rb +9 -11
  40. data/test/not_test.rb +6 -8
  41. data/test/or_test.rb +8 -10
  42. data/test/scope_test.rb +11 -13
  43. data/test/search_cop_test.rb +42 -36
  44. data/test/string_test.rb +67 -19
  45. data/test/test_helper.rb +28 -18
  46. data/test/visitor_test.rb +4 -6
  47. metadata +25 -42
  48. data/.travis.yml +0 -45
  49. data/Appraisals +0 -21
  50. data/gemfiles/3.2.gemfile +0 -25
  51. data/gemfiles/4.0.gemfile +0 -25
  52. data/gemfiles/4.1.gemfile +0 -25
  53. data/gemfiles/4.2.gemfile +0 -25
  54. 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.class.name =~ /mysql/i
11
- extend(SearchCop::Visitors::Postgres) if @connection.class.name =~ /postgres/i
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
-
@@ -1,5 +1,3 @@
1
-
2
1
  require "search_cop/visitors/visitor"
3
2
  require "search_cop/visitors/mysql"
4
3
  require "search_cop/visitors/postgres"
5
-
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 SpecificationError < StandardError; end
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 < StandardError; end
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
- self.send(:define_singleton_method, name) { |query| search_cop query, name }
45
- self.send(:define_singleton_method, "unsafe_#{name}") { |query| unsafe_search_cop query, name }
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 query, scope_name
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
- define_method method do |value|
32
- attributes.collect! { |attribute| attribute.send method, value }.inject(:or)
33
- end
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
- { :eq => "Equality", :not_eq => "NotEqual", :lt => "LessThan", :lteq => "LessThanOrEqual", :gt => "GreaterThan", :gteq => "GreaterThanOrEqual", :matches => "Matches" }.each do |method, class_name|
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.send(name, *args, &block)
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 respond_to?(*args)
153
- super(*args) || @attribute.respond_to?(*args)
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
- return value.gsub(/\*/, "%") if (options[:left_wildcard] != false && value.strip =~ /^[^*]+\*$|^\*[^*]+$/) || value.strip =~ /^[^*]+\*$/
162
+ res = value.gsub(/[%_\\]/) { |char| "\\#{char}" }
160
163
 
161
- options[:left_wildcard] != false ? "%#{value}%" : "#{value}%"
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 =~ /^[0-9]+(\.[0-9]+)?$/
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 .. value unless value.is_a?(::String)
200
-
201
- if value =~ /^[0-9]{4}$/
202
- ::Time.new(value).beginning_of_year .. ::Time.new(value).end_of_year
203
- elsif value =~ /^([0-9]{4})(\.|-|\/)([0-9]{1,2})$/
204
- ::Time.new($1, $3, 15).beginning_of_month .. ::Time.new($1, $3, 15).end_of_month
205
- elsif value =~ /^([0-9]{1,2})(\.|-|\/)([0-9]{4})$/
206
- ::Time.new($3, $1, 15).beginning_of_month .. ::Time.new($3, $1, 15).end_of_month
207
- elsif value =~ /^[0-9]{4}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{1,2}$/ || value =~ /^[0-9]{1,2}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{4}$/
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 .. time.end_of_day
210
- elsif value =~ /[0-9]{4}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{1,2}/ || value =~ /[0-9]{1,2}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{4}/
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 .. 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 .. value unless value.is_a?(::String)
246
-
247
- if value =~ /^[0-9]{4}$/
248
- ::Date.new(value.to_i).beginning_of_year .. ::Date.new(value.to_i).end_of_year
249
- elsif value =~ /^([0-9]{4})(\.|-|\/)([0-9]{1,2})$/
250
- ::Date.new($1.to_i, $3.to_i, 15).beginning_of_month .. ::Date.new($1.to_i, $3.to_i, 15).end_of_month
251
- elsif value =~ /^([0-9]{1,2})(\.|-|\/)([0-9]{4})$/
252
- ::Date.new($3.to_i, $1.to_i, 15).beginning_of_month .. ::Date.new($3.to_i, $1.to_i, 15).end_of_month
253
- elsif value =~ /[0-9]{4}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{1,2}/ || value =~ /[0-9]{1,2}(\.|-|\/)[0-9]{1,2}(\.|-|\/)[0-9]{4}/
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 .. 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
-
@@ -1,4 +1,3 @@
1
-
2
1
  require "treetop"
3
2
 
4
3
  module SearchCopGrammar
@@ -187,4 +186,3 @@ module SearchCopGrammar
187
186
  end
188
187
  end
189
188
  end
190
-
@@ -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
- attr_accessor :query_info
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.select { |element| element.class != Treetop::Runtime::SyntaxNode }
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 (space? ('AND' / 'and') space? / space !('OR' / 'or')) complex_expression <AndExpression> / 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> / [^\s()]+ <AnywhereExpression>
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> / [^\s()]+ <Value>
50
+ "'" [^\']* "'" <SingleQuotedValue> / '"' [^\"]* '"' <DoubleQuotedValue> / [^[:blank:]()]+ <Value>
49
51
  end
50
52
 
51
53
  rule space
52
- [\s]+
54
+ [[:blank:]]+
53
55
  end
54
56
  end
55
57
 
data/search_cop.gemspec CHANGED
@@ -1,29 +1,27 @@
1
- # coding: utf-8
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 'search_cop/version'
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 = %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}
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 "factory_girl"
27
- spec.add_development_dependency "appraisal"
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, :title => "Expected title", :description => "Description")
7
- rejected = create(:product, :title => "Rejected title", :description => "Description")
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, :title => "Expected title", :description => "Description")
19
- rejected = create(:product, :title => "Rejected title", :description => "Description")
17
+ expected = create(:product, title: "Expected title", description: "Description")
18
+ rejected = create(:product, title: "Rejected title", description: "Description")
20
19
 
21
- results = Product.search(:and => [{:title => "Expected title"}, {:description => "Description"}])
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, :available => true)
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, :available => false)
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, :available => true)
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, :available => true)
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, :available => true)
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, :available => false)
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: postgres
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-01"))
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, :created_on => Date.parse("2014-05-02"))
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
-