searchable-by 0.5.8 → 0.7.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 +4 -4
- data/.github/workflows/test.yml +21 -0
- data/Gemfile.lock +25 -32
- data/README.md +15 -2
- data/lib/searchable_by.rb +1 -0
- data/lib/searchable_by/column.rb +24 -12
- data/lib/searchable_by/concern.rb +11 -12
- data/lib/searchable_by/config.rb +9 -1
- data/lib/searchable_by/profiles.rb +23 -0
- data/lib/searchable_by/util.rb +9 -12
- data/searchable-by.gemspec +2 -4
- data/spec/searchable_by/util_spec.rb +3 -2
- data/spec/searchable_by_spec.rb +34 -5
- data/spec/spec_helper.rb +11 -4
- metadata +6 -19
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3c6177e1001a078408b9466190f08df81a8dc812724903650ecd4a1b267fa09
|
4
|
+
data.tar.gz: 8c08713658466f75b21cac681a4dcce20bef6dc1544c4d7a935bb1eb285f54ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f384f6380a8124c6a8a5f1591d54b14cbce6def8766cbe0271f43f023c98d0a6622f99f0ac22d7537b497ea59ea10ff7412b005729159d6d3cbc78908c00e8a6
|
7
|
+
data.tar.gz: ae93c1ce199d6a6315d579346ddff7c04dd1673782c85c130d9c17def56aa94d1873cb44333a53937d789e76683436641fd4187f1b26fd89327079d9857aefe7
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [main]
|
6
|
+
pull_request:
|
7
|
+
branches: [main]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
ruby:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby-version: ["2.6", "2.7", "3.0"]
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.ruby-version }}
|
20
|
+
bundler-cache: true
|
21
|
+
- run: bundle exec rake
|
data/Gemfile.lock
CHANGED
@@ -1,38 +1,36 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
searchable-by (0.
|
4
|
+
searchable-by (0.7.0)
|
5
5
|
activerecord
|
6
|
-
activesupport
|
7
6
|
|
8
7
|
GEM
|
9
8
|
remote: http://rubygems.org/
|
10
9
|
specs:
|
11
|
-
activemodel (6.1.
|
12
|
-
activesupport (= 6.1.
|
13
|
-
activerecord (6.1.
|
14
|
-
activemodel (= 6.1.
|
15
|
-
activesupport (= 6.1.
|
16
|
-
activesupport (6.1.
|
10
|
+
activemodel (6.1.4)
|
11
|
+
activesupport (= 6.1.4)
|
12
|
+
activerecord (6.1.4)
|
13
|
+
activemodel (= 6.1.4)
|
14
|
+
activesupport (= 6.1.4)
|
15
|
+
activesupport (6.1.4)
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
17
|
i18n (>= 1.6, < 2)
|
19
18
|
minitest (>= 5.1)
|
20
19
|
tzinfo (~> 2.0)
|
21
20
|
zeitwerk (~> 2.3)
|
22
21
|
ast (2.4.2)
|
23
|
-
concurrent-ruby (1.1.
|
22
|
+
concurrent-ruby (1.1.9)
|
24
23
|
diff-lcs (1.4.4)
|
25
|
-
i18n (1.8.
|
24
|
+
i18n (1.8.10)
|
26
25
|
concurrent-ruby (~> 1.0)
|
27
|
-
minitest (5.14.
|
26
|
+
minitest (5.14.4)
|
28
27
|
parallel (1.20.1)
|
29
|
-
parser (3.0.
|
28
|
+
parser (3.0.2.0)
|
30
29
|
ast (~> 2.4.1)
|
31
|
-
rack (2.2.3)
|
32
30
|
rainbow (3.0.0)
|
33
|
-
rake (13.0.
|
34
|
-
regexp_parser (2.
|
35
|
-
rexml (3.2.
|
31
|
+
rake (13.0.6)
|
32
|
+
regexp_parser (2.1.1)
|
33
|
+
rexml (3.2.5)
|
36
34
|
rspec (3.10.0)
|
37
35
|
rspec-core (~> 3.10.0)
|
38
36
|
rspec-expectations (~> 3.10.0)
|
@@ -46,33 +44,28 @@ GEM
|
|
46
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
47
45
|
rspec-support (~> 3.10.0)
|
48
46
|
rspec-support (3.10.2)
|
49
|
-
rubocop (1.
|
47
|
+
rubocop (1.18.3)
|
50
48
|
parallel (~> 1.10)
|
51
49
|
parser (>= 3.0.0.0)
|
52
50
|
rainbow (>= 2.2.2, < 4.0)
|
53
51
|
regexp_parser (>= 1.8, < 3.0)
|
54
52
|
rexml
|
55
|
-
rubocop-ast (>= 1.
|
53
|
+
rubocop-ast (>= 1.7.0, < 2.0)
|
56
54
|
ruby-progressbar (~> 1.7)
|
57
55
|
unicode-display_width (>= 1.4.0, < 3.0)
|
58
|
-
rubocop-ast (1.
|
59
|
-
parser (>=
|
60
|
-
rubocop-bsm (0.
|
56
|
+
rubocop-ast (1.7.0)
|
57
|
+
parser (>= 3.0.1.1)
|
58
|
+
rubocop-bsm (0.6.0)
|
61
59
|
rubocop (~> 1.0)
|
62
60
|
rubocop-performance
|
63
|
-
rubocop-rails
|
64
61
|
rubocop-rake
|
65
62
|
rubocop-rspec
|
66
|
-
rubocop-performance (1.
|
67
|
-
rubocop (>=
|
63
|
+
rubocop-performance (1.11.4)
|
64
|
+
rubocop (>= 1.7.0, < 2.0)
|
68
65
|
rubocop-ast (>= 0.4.0)
|
69
|
-
rubocop-
|
70
|
-
|
71
|
-
|
72
|
-
rubocop (>= 0.90.0, < 2.0)
|
73
|
-
rubocop-rake (0.5.1)
|
74
|
-
rubocop
|
75
|
-
rubocop-rspec (2.2.0)
|
66
|
+
rubocop-rake (0.6.0)
|
67
|
+
rubocop (~> 1.0)
|
68
|
+
rubocop-rspec (2.4.0)
|
76
69
|
rubocop (~> 1.0)
|
77
70
|
rubocop-ast (>= 1.1.0)
|
78
71
|
ruby-progressbar (1.11.0)
|
@@ -95,4 +88,4 @@ DEPENDENCIES
|
|
95
88
|
sqlite3
|
96
89
|
|
97
90
|
BUNDLED WITH
|
98
|
-
2.2.
|
91
|
+
2.2.21
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Searchable By
|
2
2
|
|
3
|
+
[](https://github.com/bsm/searchable-by/actions/workflows/test.yml)
|
4
|
+
|
3
5
|
ActiveRecord plugin to quickly create search scopes.
|
4
6
|
|
5
7
|
## Installation
|
@@ -13,12 +15,12 @@ class Post < ActiveRecord::Base
|
|
13
15
|
belongs_to :author
|
14
16
|
|
15
17
|
# Limit the number of terms per query to 3.
|
16
|
-
|
18
|
+
# Ignore search terms shorter than 3 characters (useful for trigram indexes).
|
19
|
+
searchable_by max_terms: 3, min_length: 3 do
|
17
20
|
# Allow to search strings with custom match type.
|
18
21
|
column :title,
|
19
22
|
match: :prefix, # Use btree index-friendly prefix match, e.g. `ILIKE 'term%'` instead of default `ILIKE '%term%'`.
|
20
23
|
match_phrase: :exact, # For phrases use exact match type, e.g. searching for `"My Post"` will query `WHERE LOWER(title) = 'my post'`.
|
21
|
-
min_length: 3 # Return no-match if search term is too short (useful for trigram indexes).
|
22
24
|
|
23
25
|
# ... and integers.
|
24
26
|
column :id, type: :integer
|
@@ -32,6 +34,14 @@ class Post < ActiveRecord::Base
|
|
32
34
|
joins(:author)
|
33
35
|
end
|
34
36
|
end
|
37
|
+
|
38
|
+
# Multiple profiles can be defined. This one only searches title and metadata.
|
39
|
+
searchable_by :metadata do
|
40
|
+
column :title, match: :prefix
|
41
|
+
|
42
|
+
# Allow custom wildcard replacement using a match e.g. searching for `"My*Post"` will query `ILIKE 'My%Post'`.
|
43
|
+
column :metadata, match: :prefix, wildcard: '*'
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
47
|
# Search for 'alice'
|
@@ -42,4 +52,7 @@ Post.search_by('alice "pie recipe"')
|
|
42
52
|
|
43
53
|
# Search for 'alice' but NOT for 'pie recipe'
|
44
54
|
Post.search_by('alice -"pie recipe"')
|
55
|
+
|
56
|
+
# Search using metadata profile
|
57
|
+
Post.search_by('meta', profile: :metadata)
|
45
58
|
```
|
data/lib/searchable_by.rb
CHANGED
@@ -4,6 +4,7 @@ module SearchableBy
|
|
4
4
|
autoload :Column, 'searchable_by/column'
|
5
5
|
autoload :Concern, 'searchable_by/concern'
|
6
6
|
autoload :Config, 'searchable_by/config'
|
7
|
+
autoload :Profiles, 'searchable_by/profiles'
|
7
8
|
autoload :Util, 'searchable_by/util'
|
8
9
|
|
9
10
|
Value = Struct.new(:term, :negate, :phrase)
|
data/lib/searchable_by/column.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
module SearchableBy
|
2
2
|
class Column
|
3
|
-
|
3
|
+
VALID_MATCH_TYPES = %i[all prefix exact].freeze
|
4
|
+
|
5
|
+
attr_reader :attr, :type, :match, :match_phrase, :wildcard
|
4
6
|
attr_accessor :node
|
5
7
|
|
6
|
-
def initialize(attr, type: :string, match: :all, match_phrase: nil,
|
8
|
+
def initialize(attr, type: :string, match: :all, match_phrase: nil, wildcard: nil)
|
7
9
|
@attr = attr
|
8
10
|
@type = type.to_sym
|
9
11
|
@match = match
|
10
12
|
@match_phrase = match_phrase || match
|
11
|
-
@
|
13
|
+
@wildcard = wildcard
|
14
|
+
|
15
|
+
raise ArgumentError, "invalid match option #{@match.inspect}" unless VALID_MATCH_TYPES.include? @match
|
16
|
+
raise ArgumentError, "invalid match_phrase option #{@match_phrase.inspect}" unless VALID_MATCH_TYPES.include? @match_phrase
|
12
17
|
end
|
13
18
|
|
14
19
|
def build_condition(value)
|
15
|
-
return Arel::Nodes::False.new if value.term.length < min_length # no-match
|
16
|
-
|
17
20
|
scope = node.not_eq(nil)
|
18
21
|
|
19
22
|
case type
|
@@ -38,17 +41,26 @@ module SearchableBy
|
|
38
41
|
|
39
42
|
case type
|
40
43
|
when :exact
|
41
|
-
|
42
|
-
|
44
|
+
if wildcard
|
45
|
+
escape_term!(term)
|
46
|
+
scope.and(node.matches(term))
|
47
|
+
else
|
48
|
+
term.downcase!
|
49
|
+
scope.and(node.lower.eq(term))
|
50
|
+
end
|
43
51
|
when :prefix
|
44
|
-
|
45
|
-
term.gsub!('_', '\_')
|
52
|
+
escape_term!(term)
|
46
53
|
scope.and(node.matches("#{term}%"))
|
47
|
-
else
|
48
|
-
|
49
|
-
term.gsub!('_', '\_')
|
54
|
+
else # :all (wraps term in wildcards)
|
55
|
+
escape_term!(term)
|
50
56
|
scope.and(node.matches("%#{term}%"))
|
51
57
|
end
|
52
58
|
end
|
59
|
+
|
60
|
+
def escape_term!(term)
|
61
|
+
term.gsub!('%', '\%')
|
62
|
+
term.gsub!('_', '\_')
|
63
|
+
term.gsub!(wildcard, '%') if wildcard
|
64
|
+
end
|
53
65
|
end
|
54
66
|
end
|
@@ -1,30 +1,29 @@
|
|
1
1
|
module SearchableBy
|
2
2
|
module Concern
|
3
3
|
def self.extended(base) # :nodoc:
|
4
|
-
base.class_attribute :
|
5
|
-
base.
|
4
|
+
base.class_attribute :_searchable_by_profiles, instance_accessor: false, instance_predicate: false
|
5
|
+
base._searchable_by_profiles = Profiles.new
|
6
6
|
super
|
7
7
|
end
|
8
8
|
|
9
9
|
def inherited(base) # :nodoc:
|
10
|
-
base.
|
10
|
+
base._searchable_by_profiles = _searchable_by_profiles.dup
|
11
11
|
super
|
12
12
|
end
|
13
13
|
|
14
|
-
def searchable_by(max_terms: nil, **options, &block)
|
15
|
-
|
16
|
-
|
17
|
-
_searchable_by_config.options.update(options) unless options.empty?
|
18
|
-
_searchable_by_config
|
14
|
+
def searchable_by(profile = :default, max_terms: nil, min_length: 0, **options, &block)
|
15
|
+
_searchable_by_profiles[profile].configure(max_terms, min_length, **options, &block)
|
16
|
+
_searchable_by_profiles
|
19
17
|
end
|
20
18
|
|
21
19
|
# @param [String] query the search query
|
22
20
|
# @return [ActiveRecord::Relation] the scoped relation
|
23
|
-
def search_by(query)
|
24
|
-
|
21
|
+
def search_by(query, profile: :default)
|
22
|
+
config = _searchable_by_profiles[profile]
|
23
|
+
columns = config.columns
|
25
24
|
return all if columns.empty?
|
26
25
|
|
27
|
-
values = Util.norm_values(query).first(
|
26
|
+
values = Util.norm_values(query, min_length: config.min_length).first(config.max_terms)
|
28
27
|
return all if values.empty?
|
29
28
|
|
30
29
|
columns.each do |col|
|
@@ -33,7 +32,7 @@ module SearchableBy
|
|
33
32
|
clauses = Util.build_clauses(columns, values)
|
34
33
|
return all if clauses.empty?
|
35
34
|
|
36
|
-
scope = instance_exec(&
|
35
|
+
scope = instance_exec(&config.scoping)
|
37
36
|
clauses.each do |clause|
|
38
37
|
scope = scope.where(clause)
|
39
38
|
end
|
data/lib/searchable_by/config.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
1
|
module SearchableBy
|
2
2
|
class Config
|
3
3
|
attr_reader :columns, :scoping, :options
|
4
|
-
attr_accessor :max_terms
|
4
|
+
attr_accessor :max_terms, :min_length
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@columns = []
|
8
8
|
@max_terms = 5
|
9
|
+
@min_length = 0
|
9
10
|
@options = {}
|
10
11
|
scope { all }
|
11
12
|
end
|
12
13
|
|
14
|
+
def configure(max_terms, min_length, **options, &block)
|
15
|
+
instance_eval(&block)
|
16
|
+
self.max_terms = max_terms if max_terms
|
17
|
+
self.min_length = min_length
|
18
|
+
self.options.update(options) unless options.empty?
|
19
|
+
end
|
20
|
+
|
13
21
|
def initialize_copy(other)
|
14
22
|
@columns = other.columns.dup
|
15
23
|
super
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SearchableBy
|
2
|
+
class Profiles
|
3
|
+
def initialize
|
4
|
+
@profiles = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](name)
|
8
|
+
name = name.to_sym
|
9
|
+
@profiles[name] ||= Config.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
@profiles.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize_copy(other)
|
17
|
+
@profiles = {}
|
18
|
+
other.each do |name, config|
|
19
|
+
@profiles[name] = config.dup
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/searchable_by/util.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module SearchableBy
|
2
2
|
module Util
|
3
|
-
def self.norm_values(query)
|
3
|
+
def self.norm_values(query, min_length: 0)
|
4
4
|
values = []
|
5
5
|
query = query.to_s.dup
|
6
6
|
|
@@ -10,7 +10,7 @@ module SearchableBy
|
|
10
10
|
term = Regexp.last_match(2)
|
11
11
|
negate = Regexp.last_match(1) == '-'
|
12
12
|
|
13
|
-
values.push Value.new(term, negate, true) unless term.blank?
|
13
|
+
values.push Value.new(term, negate, true) unless term.blank? || term.length < min_length
|
14
14
|
''
|
15
15
|
end
|
16
16
|
|
@@ -20,7 +20,7 @@ module SearchableBy
|
|
20
20
|
negate = term[0] == '-'
|
21
21
|
term.slice!(0) if negate || term[0] == '+'
|
22
22
|
|
23
|
-
values.push Value.new(term, negate, false) unless term.blank?
|
23
|
+
values.push Value.new(term, negate, false) unless term.blank? || term.length < min_length
|
24
24
|
end
|
25
25
|
|
26
26
|
values.uniq!
|
@@ -28,19 +28,16 @@ module SearchableBy
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.build_clauses(columns, values)
|
31
|
-
|
32
|
-
|
31
|
+
values.map do |value|
|
32
|
+
group = columns.map do |column|
|
33
33
|
column.build_condition(value)
|
34
|
-
end
|
35
|
-
|
36
|
-
next if grouping.empty?
|
34
|
+
end.tap(&:compact!)
|
35
|
+
next if group.empty?
|
37
36
|
|
38
|
-
clause =
|
37
|
+
clause = group.inject(&:or)
|
39
38
|
clause = clause.not if value.negate
|
40
39
|
clause
|
41
|
-
end
|
42
|
-
clauses.compact!
|
43
|
-
clauses
|
40
|
+
end.tap(&:compact!)
|
44
41
|
end
|
45
42
|
end
|
46
43
|
end
|
data/searchable-by.gemspec
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'searchable-by'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.7.0'
|
4
4
|
s.authors = ['Dimitrij Denissenko']
|
5
5
|
s.email = ['dimitrij@blacksquaremedia.com']
|
6
6
|
s.summary = 'Generate search scopes'
|
7
7
|
s.description = 'ActiveRecord plugin'
|
8
8
|
s.homepage = 'https://github.com/bsm/sortable-by'
|
9
|
-
s.license = '
|
9
|
+
s.license = 'Apache-2.0'
|
10
10
|
|
11
11
|
s.files = `git ls-files -z`.split("\x0").reject {|f| f.start_with?('spec/') }
|
12
12
|
s.test_files = `git ls-files -z -- spec/*`.split("\x0")
|
@@ -14,8 +14,6 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.required_ruby_version = '>= 2.6'
|
15
15
|
|
16
16
|
s.add_dependency 'activerecord'
|
17
|
-
s.add_dependency 'activesupport'
|
18
|
-
|
19
17
|
s.add_development_dependency 'bundler'
|
20
18
|
s.add_development_dependency 'rake'
|
21
19
|
s.add_development_dependency 'rspec'
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe SearchableBy::Util do
|
4
4
|
context 'with norm_values' do
|
5
|
-
def norm(str)
|
6
|
-
described_class.norm_values(str).each_with_object({}) do |val, acc|
|
5
|
+
def norm(str, **opts)
|
6
|
+
described_class.norm_values(str, **opts).each_with_object({}) do |val, acc|
|
7
7
|
acc[val.term] = val.negate
|
8
8
|
end
|
9
9
|
end
|
@@ -30,6 +30,7 @@ describe SearchableBy::Util do
|
|
30
30
|
expect(norm('+plus "in other term"')).to eq('in other term' => false, 'plus' => false)
|
31
31
|
expect(norm('with_blank \'\'')).to eq('with_blank' => false, '\'\'' => false)
|
32
32
|
expect(norm('with_blank_doubles ""')).to eq('with_blank_doubles' => false)
|
33
|
+
expect(norm('with min length', min_length: 4)).to eq('length' => false, 'with' => false)
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
data/spec/searchable_by_spec.rb
CHANGED
@@ -7,8 +7,8 @@ describe SearchableBy do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'configures correctly' do
|
10
|
-
expect(AbstractModel.
|
11
|
-
expect(Post.
|
10
|
+
expect(AbstractModel._searchable_by_profiles[:default].columns.size).to eq(1)
|
11
|
+
expect(Post._searchable_by_profiles[:default].columns.size).to eq(6)
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'generates SQL' do
|
@@ -22,6 +22,14 @@ describe SearchableBy do
|
|
22
22
|
expect(sql).not_to include(%("posts"."id"))
|
23
23
|
expect(sql).to include(%("posts"."title" LIKE 'foo\\%bar%'))
|
24
24
|
expect(sql).to include(%("posts"."body" LIKE '%foo\\%bar%'))
|
25
|
+
|
26
|
+
sql = User.search_by('uni*dom').to_sql
|
27
|
+
expect(sql).to include(%("users"."country" LIKE 'uni%dom'))
|
28
|
+
expect(sql).to include(%("users"."bio" LIKE '%uni*dom%'))
|
29
|
+
|
30
|
+
sql = User.search_by('"uni * dom"').to_sql
|
31
|
+
expect(sql).to include(%("users"."country" LIKE 'uni % dom'))
|
32
|
+
expect(sql).to include(%("users"."bio" LIKE '%uni * dom%'))
|
25
33
|
end
|
26
34
|
|
27
35
|
it 'searches' do
|
@@ -57,13 +65,23 @@ describe SearchableBy do
|
|
57
65
|
expect(Post.search_by('"ab"').pluck(:title)).to be_empty
|
58
66
|
expect(Post.search_by('"ab1"').pluck(:title)).to match_array(%w[ab1])
|
59
67
|
|
68
|
+
# country uses match: :full in combination with wildcard: '*'
|
69
|
+
expect(Post.search_by('*kingdom').pluck(:title)).to match_array(%w[ax1 ax2 ab1])
|
70
|
+
|
60
71
|
# body uses match: :all (default)
|
61
72
|
expect(Post.search_by('recip').pluck(:title)).to match_array(%w[ax1 ax2 bx1 bx2 ab1])
|
62
73
|
end
|
63
74
|
|
64
|
-
it 'supports min term length' do
|
65
|
-
|
66
|
-
expect(User.search_by('
|
75
|
+
it 'supports min term length in context' do
|
76
|
+
# values are discarded - too short for bio
|
77
|
+
expect(User.search_by('+be')).to match_array(USERS.values_at(:a, :b))
|
78
|
+
expect(User.search_by('is')).to match_array(USERS.values_at(:a, :b))
|
79
|
+
|
80
|
+
# value is used to scope
|
81
|
+
expect(User.search_by('ear')).to match_array(USERS.values_at(:a))
|
82
|
+
|
83
|
+
# one used, one discarded
|
84
|
+
expect(User.search_by('beard is')).to match_array(USERS.values_at(:a))
|
67
85
|
end
|
68
86
|
|
69
87
|
it 'searches within scopes' do
|
@@ -74,4 +92,15 @@ describe SearchableBy do
|
|
74
92
|
it 'searches integers' do
|
75
93
|
expect(Post.search_by(POSTS[:ab1].id.to_s).count).to eq(1)
|
76
94
|
end
|
95
|
+
|
96
|
+
it 'supports wildcards' do
|
97
|
+
expect(User.search_by('*uni*dom')).to match_array(USERS.values_at(:a))
|
98
|
+
expect(User.search_by('*uni*o*')).to match_array(USERS.values_at(:a, :b))
|
99
|
+
expect(User.search_by('*uni*of*dom')).to be_empty
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'supports profiles' do
|
103
|
+
expect(User.search_by('king', profile: :country_only)).to match_array(USERS.values_at(:a))
|
104
|
+
expect(User.search_by('Norris', profile: :country_only)).to be_empty
|
105
|
+
end
|
77
106
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,6 +9,7 @@ ActiveRecord::Base.connection.instance_eval do
|
|
9
9
|
create_table :users do |t|
|
10
10
|
t.string :name
|
11
11
|
t.string :bio
|
12
|
+
t.string :country
|
12
13
|
end
|
13
14
|
create_table :posts do |t|
|
14
15
|
t.integer :author_id, null: false
|
@@ -29,8 +30,13 @@ end
|
|
29
30
|
class User < AbstractModel
|
30
31
|
has_many :posts, foreign_key: :author_id
|
31
32
|
|
32
|
-
searchable_by do
|
33
|
-
column :bio
|
33
|
+
searchable_by min_length: 3 do
|
34
|
+
column :bio
|
35
|
+
column :country, wildcard: '*', match: :exact
|
36
|
+
end
|
37
|
+
|
38
|
+
searchable_by :country_only do
|
39
|
+
column :country
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
@@ -42,6 +48,7 @@ class Post < AbstractModel
|
|
42
48
|
column :title, match: :prefix, match_phrase: :exact
|
43
49
|
column :body
|
44
50
|
column proc { User.arel_table[:name] }, match: :exact
|
51
|
+
column proc { User.arel_table[:country] }, wildcard: '*', match: :exact
|
45
52
|
column { User.arel_table.alias('reviewers_posts')[:name] }
|
46
53
|
|
47
54
|
scope do
|
@@ -51,8 +58,8 @@ class Post < AbstractModel
|
|
51
58
|
end
|
52
59
|
|
53
60
|
USERS = {
|
54
|
-
a: User.create!(name: 'Alice', bio: '
|
55
|
-
b: User.create!(name: 'Bob', bio: '
|
61
|
+
a: User.create!(name: 'Alice', bio: "Chuck Norris' beard is immutable.", country: 'United Kingdom'),
|
62
|
+
b: User.create!(name: 'Bob', bio: 'Chuck Norris can divide by zero.', country: 'United States of America'),
|
56
63
|
}.freeze
|
57
64
|
|
58
65
|
POSTS = {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchable-by
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitrij Denissenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: activesupport
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: bundler
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,9 +115,9 @@ executables: []
|
|
129
115
|
extensions: []
|
130
116
|
extra_rdoc_files: []
|
131
117
|
files:
|
118
|
+
- ".github/workflows/test.yml"
|
132
119
|
- ".gitignore"
|
133
120
|
- ".rubocop.yml"
|
134
|
-
- ".travis.yml"
|
135
121
|
- Gemfile
|
136
122
|
- Gemfile.lock
|
137
123
|
- LICENSE
|
@@ -142,6 +128,7 @@ files:
|
|
142
128
|
- lib/searchable_by/column.rb
|
143
129
|
- lib/searchable_by/concern.rb
|
144
130
|
- lib/searchable_by/config.rb
|
131
|
+
- lib/searchable_by/profiles.rb
|
145
132
|
- lib/searchable_by/util.rb
|
146
133
|
- searchable-by.gemspec
|
147
134
|
- spec/searchable_by/util_spec.rb
|
@@ -149,7 +136,7 @@ files:
|
|
149
136
|
- spec/spec_helper.rb
|
150
137
|
homepage: https://github.com/bsm/sortable-by
|
151
138
|
licenses:
|
152
|
-
-
|
139
|
+
- Apache-2.0
|
153
140
|
metadata: {}
|
154
141
|
post_install_message:
|
155
142
|
rdoc_options: []
|
@@ -166,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
153
|
- !ruby/object:Gem::Version
|
167
154
|
version: '0'
|
168
155
|
requirements: []
|
169
|
-
rubygems_version: 3.
|
156
|
+
rubygems_version: 3.2.15
|
170
157
|
signing_key:
|
171
158
|
specification_version: 4
|
172
159
|
summary: Generate search scopes
|