search_cop 1.1.0 → 1.2.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +42 -0
  3. data/.rubocop.yml +128 -0
  4. data/CHANGELOG.md +14 -9
  5. data/Gemfile +4 -5
  6. data/README.md +11 -1
  7. data/Rakefile +0 -1
  8. data/docker-compose.yml +8 -1
  9. data/gemfiles/{5.1.gemfile → rails5.gemfile} +2 -2
  10. data/gemfiles/rails6.gemfile +13 -0
  11. data/lib/search_cop.rb +5 -23
  12. data/lib/search_cop/grammar_parser.rb +1 -3
  13. data/lib/search_cop/hash_parser.rb +19 -19
  14. data/lib/search_cop/helpers.rb +15 -0
  15. data/lib/search_cop/query_builder.rb +0 -2
  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.rb +0 -2
  20. data/lib/search_cop/visitors/mysql.rb +4 -2
  21. data/lib/search_cop/visitors/postgres.rb +5 -3
  22. data/lib/search_cop/visitors/visitor.rb +5 -3
  23. data/lib/search_cop_grammar.rb +1 -3
  24. data/lib/search_cop_grammar/attributes.rb +45 -34
  25. data/lib/search_cop_grammar/nodes.rb +0 -2
  26. data/search_cop.gemspec +8 -8
  27. data/test/and_test.rb +6 -8
  28. data/test/boolean_test.rb +7 -9
  29. data/test/database.yml +2 -1
  30. data/test/date_test.rb +14 -16
  31. data/test/datetime_test.rb +15 -17
  32. data/test/default_operator_test.rb +14 -10
  33. data/test/error_test.rb +2 -4
  34. data/test/float_test.rb +9 -11
  35. data/test/fulltext_test.rb +6 -8
  36. data/test/hash_test.rb +32 -34
  37. data/test/integer_test.rb +9 -11
  38. data/test/not_test.rb +6 -8
  39. data/test/or_test.rb +8 -10
  40. data/test/scope_test.rb +11 -13
  41. data/test/search_cop_test.rb +32 -34
  42. data/test/string_test.rb +67 -19
  43. data/test/test_helper.rb +13 -15
  44. data/test/visitor_test.rb +4 -6
  45. metadata +28 -13
  46. data/.travis.yml +0 -34
  47. data/gemfiles/4.2.gemfile +0 -13
data/test/integer_test.rb CHANGED
@@ -1,58 +1,57 @@
1
-
2
- require File.expand_path("../test_helper", __FILE__)
1
+ require File.expand_path("test_helper", __dir__)
3
2
 
4
3
  class IntegerTest < SearchCop::TestCase
5
4
  def test_anywhere
6
- product = create(:product, :stock => 1)
5
+ product = create(:product, stock: 1)
7
6
 
8
7
  assert_includes Product.search("1"), product
9
8
  refute_includes Product.search("0"), product
10
9
  end
11
10
 
12
11
  def test_includes
13
- product = create(:product, :stock => 1)
12
+ product = create(:product, stock: 1)
14
13
 
15
14
  assert_includes Product.search("stock: 1"), product
16
15
  refute_includes Product.search("stock: 10"), product
17
16
  end
18
17
 
19
18
  def test_equals
20
- product = create(:product, :stock => 1)
19
+ product = create(:product, stock: 1)
21
20
 
22
21
  assert_includes Product.search("stock = 1"), product
23
22
  refute_includes Product.search("stock = 0"), product
24
23
  end
25
24
 
26
25
  def test_equals_not
27
- product = create(:product, :stock => 1)
26
+ product = create(:product, stock: 1)
28
27
 
29
28
  assert_includes Product.search("stock != 0"), product
30
29
  refute_includes Product.search("stock != 1"), product
31
30
  end
32
31
 
33
32
  def test_greater
34
- product = create(:product, :stock => 1)
33
+ product = create(:product, stock: 1)
35
34
 
36
35
  assert_includes Product.search("stock > 0"), product
37
36
  refute_includes Product.search("stock < 1"), product
38
37
  end
39
38
 
40
39
  def test_greater_equals
41
- product = create(:product, :stock => 1)
40
+ product = create(:product, stock: 1)
42
41
 
43
42
  assert_includes Product.search("stock >= 1"), product
44
43
  refute_includes Product.search("stock >= 2"), product
45
44
  end
46
45
 
47
46
  def test_less
48
- product = create(:product, :stock => 1)
47
+ product = create(:product, stock: 1)
49
48
 
50
49
  assert_includes Product.search("stock < 2"), product
51
50
  refute_includes Product.search("stock < 1"), product
52
51
  end
53
52
 
54
53
  def test_less_equals
55
- product = create(:product, :stock => 1)
54
+ product = create(:product, stock: 1)
56
55
 
57
56
  assert_includes Product.search("stock <= 1"), product
58
57
  refute_includes Product.search("stock <= 0"), product
@@ -64,4 +63,3 @@ class IntegerTest < SearchCop::TestCase
64
63
  end
65
64
  end
66
65
  end
67
-
data/test/not_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 NotTest < SearchCop::TestCase
5
4
  def test_not_string
6
- expected = create(:product, :title => "Expected title")
7
- rejected = create(:product, :title => "Rejected title")
5
+ expected = create(:product, title: "Expected title")
6
+ rejected = create(:product, title: "Rejected title")
8
7
 
9
8
  results = Product.search("title: Title NOT title: Rejected")
10
9
 
@@ -15,13 +14,12 @@ class NotTest < SearchCop::TestCase
15
14
  end
16
15
 
17
16
  def test_not_hash
18
- expected = create(:product, :title => "Expected title")
19
- rejected = create(:product, :title => "Rejected title")
17
+ expected = create(:product, title: "Expected title")
18
+ rejected = create(:product, title: "Rejected title")
20
19
 
21
- results = Product.search(:and => [{:title => "Title"}, {:not => {:title => "Rejected"}}])
20
+ results = Product.search(and: [{ title: "Title" }, { not: { title: "Rejected" } }])
22
21
 
23
22
  assert_includes results, expected
24
23
  refute_includes results, rejected
25
24
  end
26
25
  end
27
-
data/test/or_test.rb CHANGED
@@ -1,11 +1,10 @@
1
-
2
- require File.expand_path("../test_helper", __FILE__)
1
+ require File.expand_path("test_helper", __dir__)
3
2
 
4
3
  class OrTest < SearchCop::TestCase
5
4
  def test_or_string
6
- product1 = create(:product, :title => "Title1")
7
- product2 = create(:product, :title => "Title2")
8
- product3 = create(:product, :title => "Title3")
5
+ product1 = create(:product, title: "Title1")
6
+ product2 = create(:product, title: "Title2")
7
+ product3 = create(:product, title: "Title3")
9
8
 
10
9
  results = Product.search("title: Title1 OR title: Title2")
11
10
 
@@ -15,15 +14,14 @@ class OrTest < SearchCop::TestCase
15
14
  end
16
15
 
17
16
  def test_or_hash
18
- product1 = create(:product, :title => "Title1")
19
- product2 = create(:product, :title => "Title2")
20
- product3 = create(:product, :title => "Title3")
17
+ product1 = create(:product, title: "Title1")
18
+ product2 = create(:product, title: "Title2")
19
+ product3 = create(:product, title: "Title3")
21
20
 
22
- results = Product.search(:or => [{:title => "Title1"}, {:title => "Title2"}])
21
+ results = Product.search(or: [{ title: "Title1" }, { title: "Title2" }])
23
22
 
24
23
  assert_includes results, product1
25
24
  assert_includes results, product2
26
25
  refute_includes results, product3
27
26
  end
28
27
  end
29
-
data/test/scope_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 ScopeTest < SearchCop::TestCase
5
4
  def test_scope_name
6
- expected = create(:product, :title => "Expected")
7
- rejected = create(:product, :notice => "Expected")
5
+ expected = create(:product, title: "Expected")
6
+ rejected = create(:product, notice: "Expected")
8
7
 
9
8
  results = Product.user_search("Expected")
10
9
 
@@ -13,8 +12,8 @@ class ScopeTest < SearchCop::TestCase
13
12
  end
14
13
 
15
14
  def test_options
16
- expected = create(:product, :title => "Expected")
17
- rejected = create(:product, :description => "Expected")
15
+ expected = create(:product, title: "Expected")
16
+ rejected = create(:product, description: "Expected")
18
17
 
19
18
  results = Product.user_search("Expected")
20
19
 
@@ -23,8 +22,8 @@ class ScopeTest < SearchCop::TestCase
23
22
  end
24
23
 
25
24
  def test_custom_scope
26
- expected = create(:product, :user => create(:user, :username => "Expected"))
27
- rejected = create(:product, :user => create(:user, :username => "Rejected"))
25
+ expected = create(:product, user: create(:user, username: "Expected"))
26
+ rejected = create(:product, user: create(:user, username: "Rejected"))
28
27
 
29
28
  results = Product.user_search("user: Expected")
30
29
 
@@ -33,8 +32,8 @@ class ScopeTest < SearchCop::TestCase
33
32
  end
34
33
 
35
34
  def test_aliases_with_association
36
- expected = create(:product, :user => create(:user, :username => "Expected"))
37
- rejected = create(:product, :user => create(:user, :username => "Rejected"))
35
+ expected = create(:product, user: create(:user, username: "Expected"))
36
+ rejected = create(:product, user: create(:user, username: "Rejected"))
38
37
 
39
38
  results = Product.search("user: Expected")
40
39
 
@@ -43,8 +42,8 @@ class ScopeTest < SearchCop::TestCase
43
42
  end
44
43
 
45
44
  def test_aliases_with_model
46
- expected = create(:product, :user => create(:user, :username => "Expected"))
47
- rejected = create(:product, :user => create(:user, :username => "Rejected"))
45
+ expected = create(:product, user: create(:user, username: "Expected"))
46
+ rejected = create(:product, user: create(:user, username: "Rejected"))
48
47
 
49
48
  results = Product.user_search("user: Expected")
50
49
 
@@ -52,4 +51,3 @@ class ScopeTest < SearchCop::TestCase
52
51
  refute_includes results, rejected
53
52
  end
54
53
  end
55
-
@@ -1,41 +1,40 @@
1
-
2
- require File.expand_path("../test_helper", __FILE__)
1
+ require File.expand_path("test_helper", __dir__)
3
2
 
4
3
  class SearchCopTest < SearchCop::TestCase
5
4
  def test_scope_before
6
- expected = create(:product, :stock => 1, :title => "Title")
7
- rejected = create(:product, :stock => 0, :title => "Title")
5
+ expected = create(:product, stock: 1, title: "Title")
6
+ rejected = create(:product, stock: 0, title: "Title")
8
7
 
9
- results = Product.where(:stock => 1).search("Title")
8
+ results = Product.where(stock: 1).search("Title")
10
9
 
11
10
  assert_includes results, expected
12
11
  refute_includes results, rejected
13
12
  end
14
13
 
15
14
  def test_scope_after
16
- expected = create(:product, :stock => 1, :title => "Title")
17
- rejected = create(:product, :stock => 0, :title => "Title")
15
+ expected = create(:product, stock: 1, title: "Title")
16
+ rejected = create(:product, stock: 0, title: "Title")
18
17
 
19
- results = Product.search("Title").where(:stock => 1)
18
+ results = Product.search("Title").where(stock: 1)
20
19
 
21
20
  assert_includes results, expected
22
21
  refute_includes results, rejected
23
22
  end
24
23
 
25
24
  def test_scope
26
- expected = create(:product, :stock => 1, :title => "Title")
27
- rejected = create(:product, :stock => 0, :title => "Title")
25
+ expected = create(:product, stock: 1, title: "Title")
26
+ rejected = create(:product, stock: 0, title: "Title")
28
27
 
29
- results = with_scope(Product.search_scopes[:search], lambda { where :stock => 1 }) { Product.search("title: Title") }
28
+ results = with_scope(Product.search_scopes[:search], -> { where stock: 1 }) { Product.search("title: Title") }
30
29
 
31
30
  assert_includes results, expected
32
31
  refute_includes results, rejected
33
32
  end
34
33
 
35
34
  def test_multi_associations
36
- product = create(:product, :comments => [
37
- create(:comment, :title => "Title1", :message => "Message1"),
38
- create(:comment, :title => "Title2", :message => "Message2")
35
+ product = create(:product, comments: [
36
+ create(:comment, title: "Title1", message: "Message1"),
37
+ create(:comment, title: "Title2", message: "Message2")
39
38
  ])
40
39
 
41
40
  assert_includes Product.search("comment: Title1 comment: Message1"), product
@@ -43,8 +42,8 @@ class SearchCopTest < SearchCop::TestCase
43
42
  end
44
43
 
45
44
  def test_single_association
46
- expected = create(:comment, :user => create(:user, :username => "Expected"))
47
- rejected = create(:comment, :user => create(:user, :username => "Rejected"))
45
+ expected = create(:comment, user: create(:user, username: "Expected"))
46
+ rejected = create(:comment, user: create(:user, username: "Rejected"))
48
47
 
49
48
  results = Comment.search("user: Expected")
50
49
 
@@ -53,8 +52,8 @@ class SearchCopTest < SearchCop::TestCase
53
52
  end
54
53
 
55
54
  def test_deep_associations
56
- expected = create(:product, :comments => [create(:comment, :user => create(:user, :username => "Expected"))])
57
- rejected = create(:product, :comments => [create(:comment, :user => create(:user, :username => "Rejected"))])
55
+ expected = create(:product, comments: [create(:comment, user: create(:user, username: "Expected"))])
56
+ rejected = create(:product, comments: [create(:comment, user: create(:user, username: "Rejected"))])
58
57
 
59
58
  results = Product.search("user: Expected")
60
59
 
@@ -63,15 +62,15 @@ class SearchCopTest < SearchCop::TestCase
63
62
  end
64
63
 
65
64
  def test_multiple
66
- product = create(:product, :comments => [create(:comment, :title => "Title", :message => "Message")])
65
+ product = create(:product, comments: [create(:comment, title: "Title", message: "Message")])
67
66
 
68
67
  assert_includes Product.search("comment: Title"), product
69
68
  assert_includes Product.search("comment: Message"), product
70
69
  end
71
70
 
72
71
  def test_default
73
- product1 = create(:product, :title => "Expected")
74
- product2 = create(:product, :description => "Expected")
72
+ product1 = create(:product, title: "Expected")
73
+ product2 = create(:product, description: "Expected")
75
74
 
76
75
  results = Product.search("Expected")
77
76
 
@@ -80,11 +79,11 @@ class SearchCopTest < SearchCop::TestCase
80
79
  end
81
80
 
82
81
  def test_custom_default_enabled
83
- product1 = create(:product, :title => "Expected")
84
- product2 = create(:product, :description => "Expected")
85
- product3 = create(:product, :brand => "Expected")
82
+ product1 = create(:product, title: "Expected")
83
+ product2 = create(:product, description: "Expected")
84
+ product3 = create(:product, brand: "Expected")
86
85
 
87
- results = with_options(Product.search_scopes[:search], :primary, :default => true) { Product.search "Expected" }
86
+ results = with_options(Product.search_scopes[:search], :primary, default: true) { Product.search "Expected" }
88
87
 
89
88
  assert_includes results, product1
90
89
  assert_includes results, product2
@@ -92,32 +91,32 @@ class SearchCopTest < SearchCop::TestCase
92
91
  end
93
92
 
94
93
  def test_custom_default_disabled
95
- product1 = create(:product, :brand => "Expected")
96
- product2 = create(:product, :notice => "Expected")
94
+ product1 = create(:product, brand: "Expected")
95
+ product2 = create(:product, notice: "Expected")
97
96
 
98
- results = with_options(Product.search_scopes[:search], :notice, :default => false) { Product.search "Expected" }
97
+ results = with_options(Product.search_scopes[:search], :notice, default: false) { Product.search "Expected" }
99
98
 
100
99
  assert_includes results, product1
101
100
  refute_includes results, product2
102
101
  end
103
102
 
104
103
  def test_count
105
- create_list :product, 2, :title => "Expected"
104
+ create_list :product, 2, title: "Expected"
106
105
 
107
106
  assert_equal 2, Product.search("Expected").count
108
107
  end
109
108
 
110
109
  def test_default_attributes_true
111
- with_options(Product.search_scopes[:search], :title, :default => true) do
112
- with_options(Product.search_scopes[:search], :description, :default => true) do
110
+ with_options(Product.search_scopes[:search], :title, default: true) do
111
+ with_options(Product.search_scopes[:search], :description, default: true) do
113
112
  assert_equal ["title", "description"], Product.search_scopes[:search].reflection.default_attributes.keys
114
113
  end
115
114
  end
116
115
  end
117
116
 
118
117
  def test_default_attributes_fales
119
- with_options(Product.search_scopes[:search], :title, :default => false) do
120
- with_options(Product.search_scopes[:search], :description, :default => false) do
118
+ with_options(Product.search_scopes[:search], :title, default: false) do
119
+ with_options(Product.search_scopes[:search], :description, default: false) do
121
120
  assert_equal Product.search_scopes[:search].reflection.attributes.keys - ["title", "description"], Product.search_scopes[:search].reflection.default_attributes.keys
122
121
  end
123
122
  end
@@ -136,4 +135,3 @@ class SearchCopTest < SearchCop::TestCase
136
135
  refute Object.respond_to?(:search)
137
136
  end
138
137
  end
139
-
data/test/string_test.rb CHANGED
@@ -1,16 +1,15 @@
1
-
2
- require File.expand_path("../test_helper", __FILE__)
1
+ require File.expand_path("test_helper", __dir__)
3
2
 
4
3
  class StringTest < SearchCop::TestCase
5
4
  def test_anywhere
6
- product = create(:product, :title => "Expected title")
5
+ product = create(:product, title: "Expected title")
7
6
 
8
7
  assert_includes Product.search("Expected"), product
9
8
  refute_includes Product.search("Rejected"), product
10
9
  end
11
10
 
12
11
  def test_anywhere_quoted
13
- product = create(:product, :title => "Expected title")
12
+ product = create(:product, title: "Expected title")
14
13
 
15
14
  assert_includes Product.search("'Expected title'"), product
16
15
  assert_includes Product.search('"Expected title"'), product
@@ -20,75 +19,124 @@ class StringTest < SearchCop::TestCase
20
19
  end
21
20
 
22
21
  def test_multiple
23
- product = create(:product, :comments => [create(:comment, :title => "Expected title", :message => "Expected message")])
22
+ product = create(:product, comments: [create(:comment, title: "Expected title", message: "Expected message")])
24
23
 
25
24
  assert_includes Product.search("Expected"), product
26
25
  refute_includes Product.search("Rejected"), product
27
26
  end
28
27
 
29
28
  def test_includes
30
- product = create(:product, :title => "Expected")
29
+ product = create(:product, title: "Expected")
31
30
 
32
31
  assert_includes Product.search("title: Expected"), product
33
32
  refute_includes Product.search("title: Rejected"), product
34
33
  end
35
34
 
35
+ def test_query_string_wildcards
36
+ product1 = create(:product, brand: "First brand")
37
+ product2 = create(:product, brand: "Second brand")
38
+
39
+ assert_equal Product.search("brand: First*"), [product1]
40
+ assert_equal Product.search("brand: br*nd"), []
41
+ assert_equal Product.search("brand: brand*"), []
42
+ assert_equal Product.search("brand: *brand*").to_set, [product1, product2].to_set
43
+ assert_equal Product.search("brand: *brand").to_set, [product1, product2].to_set
44
+ end
45
+
46
+ def test_query_string_wildcard_escaping
47
+ product1 = create(:product, brand: "som% brand")
48
+ product2 = create(:product, brand: "som_ brand")
49
+ product3 = create(:product, brand: "som\\ brand")
50
+ _product4 = create(:product, brand: "some brand")
51
+
52
+ assert_equal Product.search("brand: som% brand"), [product1]
53
+ assert_equal Product.search("brand: som_ brand"), [product2]
54
+ assert_equal Product.search("brand: som\\ brand"), [product3]
55
+ end
56
+
57
+ def test_query_string_wildcards_with_left_wildcard_false
58
+ product = create(:product, brand: "Some brand")
59
+
60
+ with_options(Product.search_scopes[:search], :brand, left_wildcard: false) do
61
+ refute_includes Product.search("brand: *brand"), product
62
+ assert_includes Product.search("brand: Some"), product
63
+ end
64
+ end
65
+
66
+ def test_query_string_wildcards_with_right_wildcard_false
67
+ product = create(:product, brand: "Some brand")
68
+
69
+ with_options(Product.search_scopes[:search], :brand, right_wildcard: false) do
70
+ refute_includes Product.search("brand: Some*"), product
71
+ assert_includes Product.search("brand: brand"), product
72
+ end
73
+ end
74
+
36
75
  def test_includes_with_left_wildcard
37
- product = create(:product, :title => "Some title")
76
+ product = create(:product, brand: "Some brand")
77
+
78
+ assert_includes Product.search("brand: brand"), product
79
+ end
38
80
 
39
- assert_includes Product.search("title: Title"), product
81
+ def test_includes_with_left_wildcard_false
82
+ expected = create(:product, brand: "Brand")
83
+ rejected = create(:product, brand: "Rejected brand")
84
+
85
+ results = with_options(Product.search_scopes[:search], :brand, left_wildcard: false) { Product.search "brand: Brand" }
86
+
87
+ assert_includes results, expected
88
+ refute_includes results, rejected
40
89
  end
41
90
 
42
- def test_includes_without_left_wildcard
43
- expected = create(:product, :brand => "Brand")
44
- rejected = create(:product, :brand => "Rejected brand")
91
+ def test_includes_with_right_wildcard_false
92
+ expected = create(:product, brand: "Brand")
93
+ rejected = create(:product, brand: "Brand rejected")
45
94
 
46
- results = with_options(Product.search_scopes[:search], :brand, :left_wildcard => false) { Product.search "brand: Brand" }
95
+ results = with_options(Product.search_scopes[:search], :brand, right_wildcard: false) { Product.search "brand: Brand" }
47
96
 
48
97
  assert_includes results, expected
49
98
  refute_includes results, rejected
50
99
  end
51
100
 
52
101
  def test_equals
53
- product = create(:product, :title => "Expected title")
102
+ product = create(:product, title: "Expected title")
54
103
 
55
104
  assert_includes Product.search("title = 'Expected title'"), product
56
105
  refute_includes Product.search("title = Expected"), product
57
106
  end
58
107
 
59
108
  def test_equals_not
60
- product = create(:product, :title => "Expected")
109
+ product = create(:product, title: "Expected")
61
110
 
62
111
  assert_includes Product.search("title != Rejected"), product
63
112
  refute_includes Product.search("title != Expected"), product
64
113
  end
65
114
 
66
115
  def test_greater
67
- product = create(:product, :title => "Title B")
116
+ product = create(:product, title: "Title B")
68
117
 
69
118
  assert_includes Product.search("title > 'Title A'"), product
70
119
  refute_includes Product.search("title > 'Title B'"), product
71
120
  end
72
121
 
73
122
  def test_greater_equals
74
- product = create(:product, :title => "Title A")
123
+ product = create(:product, title: "Title A")
75
124
 
76
125
  assert_includes Product.search("title >= 'Title A'"), product
77
126
  refute_includes Product.search("title >= 'Title B'"), product
78
127
  end
79
128
 
80
129
  def test_less
81
- product = create(:product, :title => "Title A")
130
+ product = create(:product, title: "Title A")
82
131
 
83
132
  assert_includes Product.search("title < 'Title B'"), product
84
133
  refute_includes Product.search("title < 'Title A'"), product
85
134
  end
86
135
 
87
136
  def test_less_or_greater
88
- product = create(:product, :title => "Title B")
137
+ product = create(:product, title: "Title B")
89
138
 
90
139
  assert_includes Product.search("title <= 'Title B'"), product
91
140
  refute_includes Product.search("title <= 'Title A'"), product
92
141
  end
93
142
  end
94
-