search_cop 1.0.6 → 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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +42 -0
- data/.rubocop.yml +128 -0
- data/CHANGELOG.md +36 -5
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +4 -17
- data/README.md +143 -35
- data/Rakefile +0 -1
- data/docker-compose.yml +18 -0
- data/gemfiles/rails5.gemfile +13 -0
- data/gemfiles/rails6.gemfile +13 -0
- data/lib/search_cop.rb +15 -13
- data/lib/search_cop/grammar_parser.rb +3 -4
- data/lib/search_cop/hash_parser.rb +23 -17
- data/lib/search_cop/helpers.rb +15 -0
- data/lib/search_cop/query_builder.rb +2 -4
- data/lib/search_cop/query_info.rb +0 -2
- data/lib/search_cop/search_scope.rb +8 -5
- data/lib/search_cop/version.rb +1 -1
- data/lib/search_cop/visitors.rb +0 -2
- data/lib/search_cop/visitors/mysql.rb +9 -7
- data/lib/search_cop/visitors/postgres.rb +18 -8
- data/lib/search_cop/visitors/visitor.rb +13 -6
- data/lib/search_cop_grammar.rb +18 -9
- data/lib/search_cop_grammar.treetop +6 -4
- data/lib/search_cop_grammar/attributes.rb +77 -34
- data/lib/search_cop_grammar/nodes.rb +7 -2
- data/search_cop.gemspec +9 -10
- data/test/and_test.rb +6 -8
- data/test/boolean_test.rb +7 -9
- data/test/database.yml +4 -1
- data/test/date_test.rb +38 -12
- data/test/datetime_test.rb +45 -12
- data/test/default_operator_test.rb +51 -0
- data/test/error_test.rb +2 -4
- data/test/float_test.rb +16 -11
- data/test/fulltext_test.rb +8 -10
- data/test/hash_test.rb +39 -31
- data/test/integer_test.rb +9 -11
- data/test/not_test.rb +6 -8
- data/test/or_test.rb +8 -10
- data/test/scope_test.rb +11 -13
- data/test/search_cop_test.rb +36 -34
- data/test/string_test.rb +67 -19
- data/test/test_helper.rb +24 -18
- data/test/visitor_test.rb +15 -8
- metadata +41 -55
- data/.travis.yml +0 -34
- data/Appraisals +0 -20
- data/gemfiles/3.2.gemfile +0 -26
- data/gemfiles/4.0.gemfile +0 -26
- data/gemfiles/4.1.gemfile +0 -26
- data/gemfiles/4.2.gemfile +0 -26
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8cb7e91b305057b2ea3df0c96643f8cd2cc726583f2362db9d576ef128826a75
|
|
4
|
+
data.tar.gz: 419ee38ec698795d478f7749486e63b533c9cc497057aa820b5627b343cbdeb4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 19b765a13b9a21a5b798b60618253729b4a8c1279babe47644d5c030b93b09c87706138ab11cd5bfd9daee757d8d04bff95fc1bc3ce50c5f6f7399aaaabc09d1
|
|
7
|
+
data.tar.gz: a65e72e3d6e5500b986bb312c45dbbd00432419b344765c71387432dd127efd5eb463cd42f71c3e405913a66b8d45201b9d909b9e66da17bf28f11ea3b7aa5c8
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
build:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
ruby: ["2.5", "2.6", "2.7"]
|
|
9
|
+
rails: ["rails5", "rails6"]
|
|
10
|
+
database: ["sqlite", "postgres", "mysql"]
|
|
11
|
+
services:
|
|
12
|
+
postgres:
|
|
13
|
+
image: postgres
|
|
14
|
+
env:
|
|
15
|
+
POSTGRES_USER: search_cop
|
|
16
|
+
POSTGRES_PASSWORD: secret
|
|
17
|
+
POSTGRES_DB: search_cop
|
|
18
|
+
ports:
|
|
19
|
+
- 5432:5432
|
|
20
|
+
mysql:
|
|
21
|
+
image: mysql
|
|
22
|
+
env:
|
|
23
|
+
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
|
24
|
+
MYSQL_ROOT_PASSWORD: ""
|
|
25
|
+
MYSQL_DATABASE: search_cop
|
|
26
|
+
ports:
|
|
27
|
+
- 3306:3306
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v1
|
|
30
|
+
- uses: actions/setup-ruby@v1
|
|
31
|
+
with:
|
|
32
|
+
ruby-version: ${{ matrix.ruby }}
|
|
33
|
+
- name: test
|
|
34
|
+
env:
|
|
35
|
+
DATABASE: ${{ matrix.database }}
|
|
36
|
+
run: |
|
|
37
|
+
gem install bundler
|
|
38
|
+
bundle config set --local gemfile "gemfiles/${{ matrix.rails }}.gemfile"
|
|
39
|
+
bundle config set --local path "../vendor/bundle"
|
|
40
|
+
bundle install
|
|
41
|
+
bundle exec rake test
|
|
42
|
+
bundle exec rubocop
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
|
|
4
|
+
Gemspec/RequiredRubyVersion:
|
|
5
|
+
Enabled: false
|
|
6
|
+
|
|
7
|
+
Style/CaseLikeIf:
|
|
8
|
+
Enabled: false
|
|
9
|
+
|
|
10
|
+
Style/RedundantArgument:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
Lint/EmptyBlock:
|
|
14
|
+
Enabled: false
|
|
15
|
+
|
|
16
|
+
Layout/EmptyLineBetweenDefs:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Style/FrozenStringLiteralComment:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
Lint/RedundantRequireStatement:
|
|
23
|
+
Enabled: false
|
|
24
|
+
|
|
25
|
+
Layout/ArgumentAlignment:
|
|
26
|
+
EnforcedStyle: with_fixed_indentation
|
|
27
|
+
|
|
28
|
+
Layout/FirstArrayElementIndentation:
|
|
29
|
+
EnforcedStyle: consistent
|
|
30
|
+
|
|
31
|
+
Style/PercentLiteralDelimiters:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Style/SpecialGlobalVars:
|
|
35
|
+
EnforcedStyle: use_english_names
|
|
36
|
+
|
|
37
|
+
Security/Eval:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Style/WordArray:
|
|
41
|
+
EnforcedStyle: brackets
|
|
42
|
+
|
|
43
|
+
Style/ClassAndModuleChildren:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
Style/TrivialAccessors:
|
|
47
|
+
Enabled: false
|
|
48
|
+
|
|
49
|
+
Style/Alias:
|
|
50
|
+
Enabled: false
|
|
51
|
+
|
|
52
|
+
Style/StringLiteralsInInterpolation:
|
|
53
|
+
EnforcedStyle: double_quotes
|
|
54
|
+
|
|
55
|
+
Metrics/ClassLength:
|
|
56
|
+
Enabled: false
|
|
57
|
+
|
|
58
|
+
Naming/MethodParameterName:
|
|
59
|
+
Enabled: false
|
|
60
|
+
|
|
61
|
+
Style/SymbolArray:
|
|
62
|
+
EnforcedStyle: brackets
|
|
63
|
+
|
|
64
|
+
Layout/RescueEnsureAlignment:
|
|
65
|
+
Enabled: false
|
|
66
|
+
|
|
67
|
+
Layout/LineLength:
|
|
68
|
+
Enabled: false
|
|
69
|
+
|
|
70
|
+
Metrics/MethodLength:
|
|
71
|
+
Enabled: false
|
|
72
|
+
|
|
73
|
+
Metrics/ModuleLength:
|
|
74
|
+
Enabled: false
|
|
75
|
+
|
|
76
|
+
Style/ZeroLengthPredicate:
|
|
77
|
+
Enabled: false
|
|
78
|
+
|
|
79
|
+
Metrics/PerceivedComplexity:
|
|
80
|
+
Enabled: false
|
|
81
|
+
|
|
82
|
+
Metrics/AbcSize:
|
|
83
|
+
Enabled: false
|
|
84
|
+
|
|
85
|
+
Metrics/CyclomaticComplexity:
|
|
86
|
+
Enabled: false
|
|
87
|
+
|
|
88
|
+
Metrics/BlockLength:
|
|
89
|
+
Enabled: false
|
|
90
|
+
|
|
91
|
+
Metrics/BlockNesting:
|
|
92
|
+
Enabled: false
|
|
93
|
+
|
|
94
|
+
Style/NumericPredicate:
|
|
95
|
+
Enabled: false
|
|
96
|
+
|
|
97
|
+
Naming/AccessorMethodName:
|
|
98
|
+
Enabled: false
|
|
99
|
+
|
|
100
|
+
Naming/MemoizedInstanceVariableName:
|
|
101
|
+
Enabled: false
|
|
102
|
+
|
|
103
|
+
Style/StringLiterals:
|
|
104
|
+
EnforcedStyle: double_quotes
|
|
105
|
+
|
|
106
|
+
Style/Documentation:
|
|
107
|
+
Enabled: false
|
|
108
|
+
|
|
109
|
+
Naming/ConstantName:
|
|
110
|
+
Enabled: false
|
|
111
|
+
|
|
112
|
+
Style/MutableConstant:
|
|
113
|
+
Enabled: false
|
|
114
|
+
|
|
115
|
+
Layout/MultilineMethodCallIndentation:
|
|
116
|
+
EnforcedStyle: indented
|
|
117
|
+
|
|
118
|
+
Layout/ParameterAlignment:
|
|
119
|
+
EnforcedStyle: with_fixed_indentation
|
|
120
|
+
|
|
121
|
+
Lint/UnusedMethodArgument:
|
|
122
|
+
Enabled: false
|
|
123
|
+
|
|
124
|
+
Style/IfUnlessModifier:
|
|
125
|
+
Enabled: false
|
|
126
|
+
|
|
127
|
+
Style/RedundantBegin:
|
|
128
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
|
|
2
2
|
# Changelog
|
|
3
3
|
|
|
4
|
+
Version 1.2.0:
|
|
5
|
+
|
|
6
|
+
* Added support for disabling the right wildcard
|
|
7
|
+
* Fixed escaping of wildcard chars (`_`, `%`)
|
|
8
|
+
|
|
9
|
+
Version 1.1.0:
|
|
10
|
+
|
|
11
|
+
* Adds customizable default operator to concatenate conditions (#49)
|
|
12
|
+
* Make the postgis adapter use the postgres extensions
|
|
13
|
+
|
|
14
|
+
Version 1.0.9:
|
|
15
|
+
|
|
16
|
+
* Use `[:blank:]` instead of `\s` for space (#46)
|
|
17
|
+
* Updated `SearchCop::Visitors::Visitor` to check the connection's `adapter_name` when extending. (#47)
|
|
18
|
+
* Fix for negative numeric values
|
|
19
|
+
* allow searching for relative dates, like hours, days, weeks, months or years ago
|
|
20
|
+
|
|
21
|
+
Version 1.0.8:
|
|
22
|
+
|
|
23
|
+
* No longer add search scope methods globally #34
|
|
24
|
+
|
|
25
|
+
Version 1.0.7:
|
|
26
|
+
|
|
27
|
+
* Bugfix regarding `NOT` queries in fulltext mode #32
|
|
28
|
+
* Safely handle `NULL` values for match queries
|
|
29
|
+
* Added coalesce option
|
|
30
|
+
|
|
31
|
+
Version 1.0.6:
|
|
32
|
+
|
|
33
|
+
* Fixes a bug regarding date overflows in PostgreSQL
|
|
34
|
+
|
|
4
35
|
Version 1.0.5:
|
|
5
36
|
|
|
6
37
|
* Fixes a bug regarding quotation
|
|
@@ -32,7 +63,7 @@ Version 1.0.0:
|
|
|
32
63
|
|
|
33
64
|
Version 0.0.5:
|
|
34
65
|
|
|
35
|
-
* Supporting
|
|
66
|
+
* Supporting `:default => false`
|
|
36
67
|
* Datetime/Date greater operator fix
|
|
37
68
|
* Use reflection to find associated models
|
|
38
69
|
* Providing reflection
|
|
@@ -41,16 +72,16 @@ Version 0.0.4:
|
|
|
41
72
|
|
|
42
73
|
* Fixed date attributes
|
|
43
74
|
* Fail softly for mixed datatype attributes
|
|
44
|
-
* Support custom table, class and alias names via attr_searchable_alias
|
|
75
|
+
* Support custom table, class and alias names via `attr_searchable_alias`
|
|
45
76
|
|
|
46
77
|
Version 0.0.3:
|
|
47
78
|
|
|
48
|
-
* belongs_to association fixes
|
|
79
|
+
* `belongs_to` association fixes
|
|
49
80
|
|
|
50
81
|
Version 0.0.2:
|
|
51
82
|
|
|
52
83
|
* Arel abstraction layer added
|
|
53
|
-
* count() queries resulting in "Cannot visit AttrSearchableGrammar::Nodes..." fixed
|
|
84
|
+
* `count()` queries resulting in "Cannot visit AttrSearchableGrammar::Nodes..." fixed
|
|
54
85
|
* Better error messages
|
|
55
|
-
* Model#unsafe_search added
|
|
86
|
+
* `Model#unsafe_search` added
|
|
56
87
|
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# Contributing
|
|
3
|
+
|
|
4
|
+
There are two ways to contribute: issues and pull requests.
|
|
5
|
+
|
|
6
|
+
## Issues
|
|
7
|
+
|
|
8
|
+
You are very welcome to submit a github issue in case you find a bug.
|
|
9
|
+
The more detailed, the easier to reproduce. So please be verbose.
|
|
10
|
+
|
|
11
|
+
## Pull Requests
|
|
12
|
+
|
|
13
|
+
1. Fork it
|
|
14
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
15
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
16
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
17
|
+
5. Create new Pull Request
|
|
18
|
+
|
data/Gemfile
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
source
|
|
1
|
+
source "https://rubygems.org"
|
|
2
2
|
|
|
3
3
|
# Specify your gem's dependencies in search_cop.gemspec
|
|
4
4
|
gemspec
|
|
5
5
|
|
|
6
|
-
platforms :jruby do
|
|
7
|
-
gem 'activerecord-jdbcmysql-adapter'
|
|
8
|
-
gem 'activerecord-jdbcsqlite3-adapter'
|
|
9
|
-
gem 'activerecord-jdbcpostgresql-adapter'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
6
|
platforms :ruby do
|
|
13
|
-
gem
|
|
14
|
-
gem
|
|
15
|
-
gem
|
|
7
|
+
gem "mysql2"
|
|
8
|
+
gem "pg"
|
|
9
|
+
gem "sqlite3"
|
|
16
10
|
end
|
|
17
|
-
|
|
18
|
-
platforms :rbx do
|
|
19
|
-
gem 'racc'
|
|
20
|
-
gem 'rubysl', '~> 2.0'
|
|
21
|
-
gem 'psych'
|
|
22
|
-
end
|
|
23
|
-
|
data/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# SearchCop
|
|
2
2
|
|
|
3
|
-
[](https://github.com/mrkamel/search_cop/actions?query=workflow%3Atest)
|
|
4
4
|
[](https://codeclimate.com/github/mrkamel/search_cop)
|
|
5
|
-
[](https://gemnasium.com/mrkamel/search_cop)
|
|
6
5
|
[](http://badge.fury.io/rb/search_cop)
|
|
7
6
|
|
|
8
7
|

|
|
@@ -29,24 +28,17 @@ to make optimal use of them. Read more below.
|
|
|
29
28
|
Complex hash-based queries are supported as well:
|
|
30
29
|
|
|
31
30
|
```ruby
|
|
32
|
-
Book.search(:
|
|
33
|
-
Book.search(:
|
|
34
|
-
Book.search(:
|
|
35
|
-
Book.search(:
|
|
31
|
+
Book.search(author: "Rowling", title: "Harry Potter")
|
|
32
|
+
Book.search(or: [{author: "Rowling"}, {author: "Tolkien"}])
|
|
33
|
+
Book.search(and: [{price: {gt: 10}}, {not: {stock: 0}}, or: [{title: "Potter"}, {author: "Rowling"}]])
|
|
34
|
+
Book.search(or: [{query: "Rowling -Potter"}, {query: "Tolkien -Rings"}])
|
|
35
|
+
Book.search(title: {my_custom_sql_query: "Rowl"}})
|
|
36
36
|
# ...
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
## AttrSearchable is now SearchCop
|
|
40
|
-
|
|
41
|
-
As the set of features of AttrSearchable grew and grew, it has been neccessary
|
|
42
|
-
to change its DSL and name, as no `attr_searchable` method is present anymore.
|
|
43
|
-
The new DSL is cleaner and more concise. Morever, the migration process is
|
|
44
|
-
simple. Please take a look into the migration guide
|
|
45
|
-
[MIGRATION.md](https://github.com/mrkamel/search_cop/blob/master/MIGRATION.md)
|
|
46
|
-
|
|
47
39
|
## Installation
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
Add this line to your application's Gemfile:
|
|
50
42
|
|
|
51
43
|
gem 'search_cop'
|
|
52
44
|
|
|
@@ -69,8 +61,8 @@ class Book < ActiveRecord::Base
|
|
|
69
61
|
|
|
70
62
|
search_scope :search do
|
|
71
63
|
attributes :title, :description, :stock, :price, :created_at, :available
|
|
72
|
-
attributes :
|
|
73
|
-
attributes :
|
|
64
|
+
attributes comment: ["comments.title", "comments.message"]
|
|
65
|
+
attributes author: "author.name"
|
|
74
66
|
# ...
|
|
75
67
|
end
|
|
76
68
|
|
|
@@ -122,9 +114,27 @@ As `Book.search(...)` returns an `ActiveRecord::Relation`, you are free to pre-
|
|
|
122
114
|
or post-process the search results in every possible way:
|
|
123
115
|
|
|
124
116
|
```ruby
|
|
125
|
-
Book.where(:
|
|
117
|
+
Book.where(available: true).search("Harry Potter").order("books.id desc").paginate(page: params[:page])
|
|
126
118
|
```
|
|
127
119
|
|
|
120
|
+
## Security
|
|
121
|
+
|
|
122
|
+
When you pass a query string to SearchCop, it gets parsed, analyzed and mapped
|
|
123
|
+
to finally build up an SQL query. To be more precise, when SearchCop parses the
|
|
124
|
+
query, it creates objects (nodes), which represent the query expressions (And-,
|
|
125
|
+
Or-, Not-, String-, Date-, etc Nodes). To build the SQL query, SearchCop uses
|
|
126
|
+
the concept of visitors like e.g. used in
|
|
127
|
+
[Arel](https://github.com/rails/arel), such that, for every node there must be
|
|
128
|
+
a [visitor](https://github.com/mrkamel/search_cop/blob/master/lib/search_cop/visitors/visitor.rb),
|
|
129
|
+
which transforms the node to SQL. When there is no visitor, an exception is
|
|
130
|
+
raised when the query builder tries to "visit" the node. The visitors are
|
|
131
|
+
responsible for sanitizing the user supplied input. This is primilarly done via
|
|
132
|
+
quoting (string-, table-name-, column-quoting, etc). SearchCop is using the
|
|
133
|
+
methods provided by the ActiveRecord connection adapter for sanitizing/quoting
|
|
134
|
+
to prevent SQL injection. While we can never be 100% safe from security issues,
|
|
135
|
+
SearchCop takes security issues seriously. Please report responsibly via
|
|
136
|
+
security at flakks dot com in case you find any security related issues.
|
|
137
|
+
|
|
128
138
|
## Fulltext index capabilities
|
|
129
139
|
|
|
130
140
|
By default, i.e. if you don't tell SearchCop about your fulltext indices,
|
|
@@ -172,12 +182,12 @@ search in, such that SearchCop must no longer search within all fields:
|
|
|
172
182
|
|
|
173
183
|
```ruby
|
|
174
184
|
search_scope :search do
|
|
175
|
-
attributes :
|
|
185
|
+
attributes all: [:author, :title]
|
|
176
186
|
|
|
177
|
-
options :all, :type => :fulltext, :
|
|
187
|
+
options :all, :type => :fulltext, default: true
|
|
178
188
|
|
|
179
|
-
# Use :
|
|
180
|
-
# Use :
|
|
189
|
+
# Use default: true to explicitly enable fields as default fields (whitelist approach)
|
|
190
|
+
# Use default: false to explicitly disable fields as default fields (blacklist approach)
|
|
181
191
|
end
|
|
182
192
|
```
|
|
183
193
|
|
|
@@ -255,6 +265,23 @@ Regarding compound indices for PostgreSQL, use:
|
|
|
255
265
|
ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))"
|
|
256
266
|
```
|
|
257
267
|
|
|
268
|
+
To handle NULL values with PostgreSQL correctly, use COALESCE both at index
|
|
269
|
+
creation time and when specifying the `search_scope`:
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', COALESCE(author, '') || ' ' || COALESCE(title, '')))"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
plus:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
search_scope :search do
|
|
279
|
+
attributes :title
|
|
280
|
+
|
|
281
|
+
options :title, :type => :fulltext, coalesce: true
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
258
285
|
To use another PostgreSQL dictionary than `simple`, you have to create the
|
|
259
286
|
index accordingly and you need tell SearchCop about it, e.g.:
|
|
260
287
|
|
|
@@ -262,7 +289,7 @@ index accordingly and you need tell SearchCop about it, e.g.:
|
|
|
262
289
|
search_scope :search do
|
|
263
290
|
attributes :title
|
|
264
291
|
|
|
265
|
-
options :title, :type => :fulltext, :
|
|
292
|
+
options :title, :type => :fulltext, dictionary: "english"
|
|
266
293
|
end
|
|
267
294
|
```
|
|
268
295
|
|
|
@@ -273,7 +300,7 @@ For more details about PostgreSQL fulltext indices visit
|
|
|
273
300
|
|
|
274
301
|
In case you expose non-fulltext attributes to search queries (price, stock,
|
|
275
302
|
etc.), the respective queries, like `Book.search("stock > 0")`, will profit
|
|
276
|
-
from
|
|
303
|
+
from the usual non-fulltext indices. Thus, you should add a usual index on
|
|
277
304
|
every column you expose to search queries plus a fulltext index for every
|
|
278
305
|
fulltext attribute.
|
|
279
306
|
|
|
@@ -289,7 +316,7 @@ class User < ActiveRecord::Base
|
|
|
289
316
|
search_scope :search do
|
|
290
317
|
attributes :username
|
|
291
318
|
|
|
292
|
-
options :username, :
|
|
319
|
+
options :username, left_wildcard: false
|
|
293
320
|
end
|
|
294
321
|
|
|
295
322
|
# ...
|
|
@@ -302,6 +329,51 @@ User.search("admin")
|
|
|
302
329
|
# ... WHERE users.username LIKE 'admin%'
|
|
303
330
|
```
|
|
304
331
|
|
|
332
|
+
Similarly, you can disable the right wildcard as well:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
search_scope :search do
|
|
336
|
+
attributes :username
|
|
337
|
+
|
|
338
|
+
options :username, right_wildcard: false
|
|
339
|
+
end
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Default operator
|
|
343
|
+
|
|
344
|
+
When you define multiple fields on a search scope, SearcCop will use
|
|
345
|
+
by default the AND operator to concatenate the conditions, e.g:
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
class User < ActiveRecord::Base
|
|
349
|
+
include SearchCop
|
|
350
|
+
|
|
351
|
+
search_scope :search do
|
|
352
|
+
attributes :username, :fullname
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# ...
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
So a search like `User.search("something")` will generate a query
|
|
360
|
+
with the following conditions:
|
|
361
|
+
|
|
362
|
+
```sql
|
|
363
|
+
... WHERE username LIKE '%something%' AND fullname LIKE '%something%'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
However, there are cases where using AND as the default operator is not desired,
|
|
367
|
+
so SearchCop allows you to override it and use OR as the default operator instead.
|
|
368
|
+
A query like `User.search("something", default_operator: :or)` will
|
|
369
|
+
generate the query using OR to concatenate the conditions
|
|
370
|
+
|
|
371
|
+
```sql
|
|
372
|
+
... WHERE username LIKE '%something%' OR fullname LIKE '%something%'
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Finally, please note that you can apply it to fulltext indices/queries as well.
|
|
376
|
+
|
|
305
377
|
## Associations
|
|
306
378
|
|
|
307
379
|
If you specify searchable attributes from another model, like
|
|
@@ -313,7 +385,7 @@ class Book < ActiveRecord::Base
|
|
|
313
385
|
belongs_to :author
|
|
314
386
|
|
|
315
387
|
search_scope :search do
|
|
316
|
-
attributes :
|
|
388
|
+
attributes author: "author.name"
|
|
317
389
|
end
|
|
318
390
|
|
|
319
391
|
# ...
|
|
@@ -347,13 +419,13 @@ class Book < ActiveRecord::Base
|
|
|
347
419
|
# ...
|
|
348
420
|
|
|
349
421
|
search_scope :search do
|
|
350
|
-
attributes :
|
|
422
|
+
attributes similar: ["similar_books.title", "similar_books.description"]
|
|
351
423
|
|
|
352
424
|
scope do
|
|
353
425
|
joins "left outer join books similar_books on ..."
|
|
354
426
|
end
|
|
355
427
|
|
|
356
|
-
aliases :
|
|
428
|
+
aliases similar_books: Book # Tell SearchCop how to map SQL aliases to models
|
|
357
429
|
end
|
|
358
430
|
|
|
359
431
|
# ...
|
|
@@ -370,7 +442,7 @@ class Book < ActiveRecord::Base
|
|
|
370
442
|
has_many :users, :through => :comments
|
|
371
443
|
|
|
372
444
|
search_scope :search do
|
|
373
|
-
attributes :
|
|
445
|
+
attributes user: "users.username"
|
|
374
446
|
end
|
|
375
447
|
|
|
376
448
|
# ...
|
|
@@ -393,7 +465,7 @@ class Book < ActiveRecord::Base
|
|
|
393
465
|
belongs_to :user
|
|
394
466
|
|
|
395
467
|
search_scope :search do
|
|
396
|
-
attributes :
|
|
468
|
+
attributes user: ["user.username", "users_books.username"]
|
|
397
469
|
end
|
|
398
470
|
|
|
399
471
|
# ...
|
|
@@ -431,12 +503,48 @@ Query string queries support `AND/and`, `OR/or`, `:`, `=`, `!=`, `<`, `<=`,
|
|
|
431
503
|
and `matches`, `OR` has precedence over `AND`. `NOT` can only be used as infix
|
|
432
504
|
operator regarding a single attribute.
|
|
433
505
|
|
|
434
|
-
Hash based queries support
|
|
435
|
-
of
|
|
436
|
-
|
|
437
|
-
arguments. Moreover,
|
|
506
|
+
Hash based queries support `and: [...]` and `or: [...]`, which take an array
|
|
507
|
+
of `not: {...}`, `matches: {...}`, `eq: {...}`, `not_eq: {...}`,
|
|
508
|
+
`lt: {...}`, `lteq: {...}`, `gt: {...}`, `gteq: {...}` and `query: "..."`
|
|
509
|
+
arguments. Moreover, `query: "..."` makes it possible to create sub-queries.
|
|
438
510
|
The other rules for query string queries apply to hash based queries as well.
|
|
439
511
|
|
|
512
|
+
### Custom operators (Hash based queries)
|
|
513
|
+
|
|
514
|
+
SearchCop also provides the ability to define custom operators by defining a
|
|
515
|
+
`generator` in `search_scope`. They can then be used with the hash based query
|
|
516
|
+
search. This is useful when you want to use database operators that are not
|
|
517
|
+
supported by SearchCop.
|
|
518
|
+
|
|
519
|
+
Please note, when using generators, you are responsible for sanitizing/quoting
|
|
520
|
+
the values (see example below). Otherwise your generator will allow SQL
|
|
521
|
+
injection. Thus, please only use generators if you know what you're doing.
|
|
522
|
+
|
|
523
|
+
For example, if you wanted to perform a `LIKE` query where a book title starts
|
|
524
|
+
with a string, you can define the search scope like so:
|
|
525
|
+
|
|
526
|
+
```ruby
|
|
527
|
+
search_scope :search do
|
|
528
|
+
attributes :title
|
|
529
|
+
|
|
530
|
+
generator :starts_with do |column_name, raw_value|
|
|
531
|
+
pattern = "#{raw_value}%"
|
|
532
|
+
"#{column_name} LIKE #{quote pattern}"
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
When you want to perform the search you use it like this:
|
|
538
|
+
|
|
539
|
+
```ruby
|
|
540
|
+
Book.search(title: { starts_with: "The Great" })
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Security Note: The query returned from the generator will be interpolated
|
|
544
|
+
directly into the query that goes to your database. This opens up a potential
|
|
545
|
+
SQL Injection point in your app. If you use this feature you'll want to make
|
|
546
|
+
sure the query you're returning is safe to execute.
|
|
547
|
+
|
|
440
548
|
## Mapping
|
|
441
549
|
|
|
442
550
|
When searching in boolean, datetime, timestamp, etc. fields, SearchCop
|
|
@@ -521,7 +629,7 @@ class Product < ActiveRecord::Base
|
|
|
521
629
|
search_scope :search do
|
|
522
630
|
attributes :title, :description
|
|
523
631
|
|
|
524
|
-
options :title, :
|
|
632
|
+
options :title, default: true
|
|
525
633
|
end
|
|
526
634
|
end
|
|
527
635
|
|