search_cop 1.1.0 → 1.2.2

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 (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 => "../"