search_cop 1.1.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +50 -0
  3. data/.rubocop.yml +134 -0
  4. data/CHANGELOG.md +23 -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/gemfiles/rails7.gemfile +13 -0
  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 +1 -3
  16. data/lib/search_cop/query_info.rb +0 -2
  17. data/lib/search_cop/search_scope.rb +3 -5
  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 +5 -3
  22. data/lib/search_cop/visitors.rb +0 -2
  23. data/lib/search_cop.rb +5 -23
  24. data/lib/search_cop_grammar/attributes.rb +45 -34
  25. data/lib/search_cop_grammar/nodes.rb +0 -2
  26. data/lib/search_cop_grammar.rb +1 -3
  27. data/search_cop.gemspec +8 -9
  28. data/test/and_test.rb +6 -8
  29. data/test/boolean_test.rb +7 -9
  30. data/test/database.yml +2 -1
  31. data/test/date_test.rb +14 -16
  32. data/test/datetime_test.rb +15 -17
  33. data/test/default_operator_test.rb +14 -10
  34. data/test/error_test.rb +2 -4
  35. data/test/float_test.rb +9 -11
  36. data/test/fulltext_test.rb +6 -8
  37. data/test/hash_test.rb +32 -34
  38. data/test/integer_test.rb +9 -11
  39. data/test/namespace_test.rb +23 -0
  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 +41 -34
  44. data/test/string_test.rb +67 -19
  45. data/test/test_helper.rb +34 -15
  46. data/test/visitor_test.rb +4 -6
  47. metadata +34 -35
  48. data/.travis.yml +0 -34
  49. data/gemfiles/4.2.gemfile +0 -13
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
-
data/test/test_helper.rb CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  require "search_cop"
3
2
 
4
3
  begin
@@ -18,7 +17,7 @@ require "yaml"
18
17
 
19
18
  DATABASE = ENV["DATABASE"] || "sqlite"
20
19
 
21
- ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("../database.yml", __FILE__))[DATABASE]
20
+ ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("database.yml", __dir__))[DATABASE]
22
21
 
23
22
  class User < ActiveRecord::Base; end
24
23
 
@@ -28,7 +27,7 @@ class Comment < ActiveRecord::Base
28
27
  belongs_to :user
29
28
 
30
29
  search_scope :search do
31
- attributes :user => "user.username"
30
+ attributes user: "user.username"
32
31
  attributes :title, :message
33
32
  end
34
33
  end
@@ -38,19 +37,19 @@ class Product < ActiveRecord::Base
38
37
 
39
38
  search_scope :search do
40
39
  attributes :title, :description, :brand, :notice, :stock, :price, :created_at, :created_on, :available
41
- attributes :comment => ["comments.title", "comments.message"], :user => ["users.username", "users_products.username"]
42
- attributes :primary => [:title, :description]
40
+ attributes comment: ["comments.title", "comments.message"], user: ["users.username", "users_products.username"]
41
+ attributes primary: [:title, :description]
43
42
 
44
- aliases :users_products => :user
43
+ aliases users_products: :user
45
44
 
46
45
  if DATABASE != "sqlite"
47
- options :title, :type => :fulltext, coalesce: true
48
- options :description, :type => :fulltext, coalesce: true
49
- options :comment, :type => :fulltext, coalesce: true
46
+ options :title, type: :fulltext, coalesce: true
47
+ options :description, type: :fulltext, coalesce: true
48
+ options :comment, type: :fulltext, coalesce: true
50
49
  end
51
50
 
52
51
  if DATABASE == "postgres"
53
- options :title, :dictionary => "english"
52
+ options :title, dictionary: "english"
54
53
  end
55
54
 
56
55
  generator :custom_eq do |column_name, raw_value|
@@ -62,10 +61,10 @@ class Product < ActiveRecord::Base
62
61
  scope { joins "LEFT OUTER JOIN users users_products ON users_products.id = products.user_id" }
63
62
 
64
63
  attributes :title, :description
65
- attributes :user => "users_products.username"
64
+ attributes user: "users_products.username"
66
65
 
67
- options :title, :default => true
68
- aliases :users_products => User
66
+ options :title, default: true
67
+ aliases users_products: User
69
68
  end
70
69
 
71
70
  search_scope :search_multi_columns do
@@ -73,15 +72,36 @@ class Product < ActiveRecord::Base
73
72
  end
74
73
 
75
74
  has_many :comments
76
- has_many :users, :through => :comments
75
+ has_many :users, through: :comments
77
76
 
78
77
  belongs_to :user
79
78
  end
80
79
 
80
+ module SomeNamespace
81
+ class Product < ActiveRecord::Base
82
+ include SearchCop
83
+
84
+ belongs_to :user
85
+
86
+ search_scope :search do
87
+ attributes :title, :description
88
+ attributes user: ["user.username"]
89
+ end
90
+ end
91
+ end
92
+
93
+ class AvailableProduct < Product
94
+ default_scope { where(available: true) }
95
+ end
96
+
81
97
  FactoryBot.define do
82
98
  factory :product do
83
99
  end
84
100
 
101
+ factory :available_product do
102
+ available { true }
103
+ end
104
+
85
105
  factory :comment do
86
106
  end
87
107
 
@@ -173,4 +193,3 @@ class SearchCop::TestCase
173
193
  ActiveRecord::Base.connection.quote object
174
194
  end
175
195
  end
176
-
data/test/visitor_test.rb CHANGED
@@ -1,5 +1,4 @@
1
-
2
- require File.expand_path("../test_helper", __FILE__)
1
+ require File.expand_path("test_helper", __dir__)
3
2
 
4
3
  class VisitorTest < SearchCop::TestCase
5
4
  def test_and
@@ -53,8 +52,8 @@ class VisitorTest < SearchCop::TestCase
53
52
  def test_matches
54
53
  node = SearchCopGrammar::Attributes::String.new(Product, "products", "notice").matches("Notice")
55
54
 
56
- assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} LIKE #{quote "%Notice%"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] != "postgres"
57
- assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} ILIKE #{quote "%Notice%"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
55
+ assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} LIKE #{quote "%Notice%"} ESCAPE #{quote "\\"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] != "postgres"
56
+ assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} ILIKE #{quote "%Notice%"} ESCAPE #{quote "\\"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
58
57
  end
59
58
 
60
59
  def test_not
@@ -99,7 +98,7 @@ class VisitorTest < SearchCop::TestCase
99
98
  end
100
99
 
101
100
  def test_generator
102
- generator = ->(column_name, value) do
101
+ generator = lambda do |column_name, value|
103
102
  "#{column_name} = #{quote value}"
104
103
  end
105
104
  node = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").generator(generator, "value").optimize!
@@ -107,4 +106,3 @@ class VisitorTest < SearchCop::TestCase
107
106
  assert_equal "#{quote_table_name "products"}.#{quote_column_name "title"} = 'value'", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
108
107
  end
109
108
  end
110
-
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_cop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Vetter
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-16 00:00:00.000000000 Z
11
+ date: 2022-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: treetop
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +53,7 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: rake
56
+ name: factory_bot
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
@@ -53,21 +67,21 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
- name: activerecord
70
+ name: minitest
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: 3.0.0
75
+ version: '0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: 3.0.0
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: factory_bot
84
+ name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -81,7 +95,7 @@ dependencies:
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
- name: minitest
98
+ name: rubocop
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
@@ -101,8 +115,9 @@ executables: []
101
115
  extensions: []
102
116
  extra_rdoc_files: []
103
117
  files:
118
+ - ".github/workflows/test.yml"
104
119
  - ".gitignore"
105
- - ".travis.yml"
120
+ - ".rubocop.yml"
106
121
  - CHANGELOG.md
107
122
  - CONTRIBUTING.md
108
123
  - Gemfile
@@ -111,11 +126,13 @@ files:
111
126
  - README.md
112
127
  - Rakefile
113
128
  - docker-compose.yml
114
- - gemfiles/4.2.gemfile
115
- - gemfiles/5.1.gemfile
129
+ - gemfiles/rails5.gemfile
130
+ - gemfiles/rails6.gemfile
131
+ - gemfiles/rails7.gemfile
116
132
  - lib/search_cop.rb
117
133
  - lib/search_cop/grammar_parser.rb
118
134
  - lib/search_cop/hash_parser.rb
135
+ - lib/search_cop/helpers.rb
119
136
  - lib/search_cop/query_builder.rb
120
137
  - lib/search_cop/query_info.rb
121
138
  - lib/search_cop/search_scope.rb
@@ -140,6 +157,7 @@ files:
140
157
  - test/fulltext_test.rb
141
158
  - test/hash_test.rb
142
159
  - test/integer_test.rb
160
+ - test/namespace_test.rb
143
161
  - test/not_test.rb
144
162
  - test/or_test.rb
145
163
  - test/scope_test.rb
@@ -151,7 +169,7 @@ homepage: https://github.com/mrkamel/search_cop
151
169
  licenses:
152
170
  - MIT
153
171
  metadata: {}
154
- post_install_message:
172
+ post_install_message:
155
173
  rdoc_options: []
156
174
  require_paths:
157
175
  - lib
@@ -166,28 +184,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
184
  - !ruby/object:Gem::Version
167
185
  version: '0'
168
186
  requirements: []
169
- rubyforge_project:
170
- rubygems_version: 2.7.3
171
- signing_key:
187
+ rubygems_version: 3.3.3
188
+ signing_key:
172
189
  specification_version: 4
173
190
  summary: Easily perform complex search engine like fulltext queries on your ActiveRecord
174
191
  models
175
- test_files:
176
- - test/and_test.rb
177
- - test/boolean_test.rb
178
- - test/database.yml
179
- - test/date_test.rb
180
- - test/datetime_test.rb
181
- - test/default_operator_test.rb
182
- - test/error_test.rb
183
- - test/float_test.rb
184
- - test/fulltext_test.rb
185
- - test/hash_test.rb
186
- - test/integer_test.rb
187
- - test/not_test.rb
188
- - test/or_test.rb
189
- - test/scope_test.rb
190
- - test/search_cop_test.rb
191
- - test/string_test.rb
192
- - test/test_helper.rb
193
- - test/visitor_test.rb
192
+ test_files: []
data/.travis.yml DELETED
@@ -1,34 +0,0 @@
1
-
2
- services:
3
- - postgresql
4
- - mysql
5
-
6
- before_script:
7
- - mysql -e 'create database search_cop;'
8
- - psql -c "create user search_cop password 'secret';" -U postgres
9
- - psql -c 'create database search_cop owner search_cop;' -U postgres
10
-
11
- matrix:
12
- include:
13
- - rvm: ruby-head
14
- gemfile: gemfiles/4.2.gemfile
15
- env: DATABASE=sqlite
16
- - rvm: ruby-head
17
- gemfile: gemfiles/4.2.gemfile
18
- env: DATABASE=mysql
19
- - rvm: ruby-head
20
- gemfile: gemfiles/5.1.gemfile
21
- env: DATABASE=mysql
22
- - rvm: ruby-head
23
- gemfile: gemfiles/4.2.gemfile
24
- env: DATABASE=postgres
25
- - rvm: ruby-head
26
- gemfile: gemfiles/5.1.gemfile
27
- env: DATABASE=postgres
28
-
29
- install:
30
- - "travis_retry bundle install"
31
-
32
- script: "rake test --trace"
33
- sudo: false
34
-
data/gemfiles/4.2.gemfile DELETED
@@ -1,13 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.2"
6
-
7
- platforms :ruby do
8
- gem "sqlite3", "1.3.13"
9
- gem "mysql2", "0.4.10"
10
- gem "pg", "0.21.0"
11
- end
12
-
13
- gemspec :path => "../"