searchable-by 0.5.9 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b6f1b26859c89dfec031b51501c3f1dd6e8050d27bf7ae00bdb0ef9b8495422
4
- data.tar.gz: a24dc8497c85648498fccffd56aaa4f995d7c31b48ab21e970356115a6a1ec50
3
+ metadata.gz: 615602791390c9de0ab159cbbd0c81c52c0441f61ad559082dad189787c7606f
4
+ data.tar.gz: ad765f8a22cd76196f7a04b7a7e16f38b3bbccacda0f1c7bda659e3ab2783f4f
5
5
  SHA512:
6
- metadata.gz: 33f4d29187d4bf9545af72632b1c17742be5ceeb3b699d36dc6e0f251151dabb06877269378d17f72dcb152f4413265866b9e076c537f5f0e1cf3f12689d3492
7
- data.tar.gz: 6cba46e2c5ee6bf164490d5f7e2b7c715ae19dcc51d6eafbc3e93283e4d02e887e926eadc821753cf4b25522b5adb4322ac37a89d678beb76c33b7fd79fb193a
6
+ metadata.gz: ae524aa23d507f4339592c4c6a2955d3d0664e7f9829be7071ed1d28a065e1ad77ff9a810a800769cf417db4d315344a487b058530e168325403cb26a1832e12
7
+ data.tar.gz: 71a1349d3738997571306d5326cf81bef66b101bbd03b9451d3b3d9b5851c35f1d732486d96b29d73e1efce792a0c5c840c42442a2f83bd5be171a47be28a17c
@@ -1,4 +1,4 @@
1
- name: Ruby
1
+ name: Test
2
2
 
3
3
  on:
4
4
  push:
@@ -7,7 +7,7 @@ on:
7
7
  branches: [main]
8
8
 
9
9
  jobs:
10
- test:
10
+ ruby:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
data/Gemfile.lock CHANGED
@@ -1,38 +1,36 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- searchable-by (0.5.9)
4
+ searchable-by (0.6.0)
5
5
  activerecord
6
- activesupport
7
6
 
8
7
  GEM
9
8
  remote: http://rubygems.org/
10
9
  specs:
11
- activemodel (6.1.3)
12
- activesupport (= 6.1.3)
13
- activerecord (6.1.3)
14
- activemodel (= 6.1.3)
15
- activesupport (= 6.1.3)
16
- activesupport (6.1.3)
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.8)
22
+ concurrent-ruby (1.1.9)
24
23
  diff-lcs (1.4.4)
25
- i18n (1.8.9)
24
+ i18n (1.8.10)
26
25
  concurrent-ruby (~> 1.0)
27
26
  minitest (5.14.4)
28
27
  parallel (1.20.1)
29
- parser (3.0.0.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.3)
31
+ rake (13.0.6)
34
32
  regexp_parser (2.1.1)
35
- rexml (3.2.4)
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.11.0)
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.2.0, < 2.0)
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.4.1)
59
- parser (>= 2.7.1.5)
60
- rubocop-bsm (0.5.4)
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.10.1)
67
- rubocop (>= 0.90.0, < 2.0)
63
+ rubocop-performance (1.11.4)
64
+ rubocop (>= 1.7.0, < 2.0)
68
65
  rubocop-ast (>= 0.4.0)
69
- rubocop-rails (2.9.1)
70
- activesupport (>= 4.2.0)
71
- rack (>= 1.1)
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.5
91
+ 2.2.21
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Searchable By
2
2
 
3
+ [![Test](https://github.com/bsm/searchable-by/actions/workflows/test.yml/badge.svg)](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
- searchable_by max_terms: 3 do
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
@@ -1,20 +1,25 @@
1
1
  module SearchableBy
2
2
  class Column
3
- attr_reader :attr, :type, :match, :match_phrase, :min_length, :wildcard
3
+ attr_reader :attr, :type, :match, :match_phrase, :wildcard
4
4
  attr_accessor :node
5
5
 
6
- def initialize(attr, type: :string, match: :all, match_phrase: nil, min_length: 0, wildcard: nil) # rubocop:disable Metrics/ParameterLists
6
+ def initialize(attr, type: :string, match: :all, match_phrase: nil, wildcard: nil, **opts) # rubocop:disable Metrics/ParameterLists
7
+ if opts.key?(:min_length)
8
+ ActiveSupport::Deprecation.warn(
9
+ 'Setting min_length for individual columns is deprecated and will be removed in the next release.' \
10
+ 'Please pass it as an option to searchable_by instead',
11
+ )
12
+ end
13
+
7
14
  @attr = attr
8
15
  @type = type.to_sym
9
16
  @match = match
10
17
  @match_phrase = match_phrase || match
11
- @min_length = min_length
18
+ @min_length = opts[:min_length].to_i
12
19
  @wildcard = wildcard
13
20
  end
14
21
 
15
22
  def build_condition(value)
16
- return Arel::Nodes::False.new if value.term.length < min_length # no-match
17
-
18
23
  scope = node.not_eq(nil)
19
24
 
20
25
  case type
@@ -27,6 +32,11 @@ module SearchableBy
27
32
 
28
33
  private
29
34
 
35
+ # TODO: remove when removing min_length option from columns
36
+ def usable?(value)
37
+ value.term.length >= @min_length
38
+ end
39
+
30
40
  def int_condition(scope, value)
31
41
  scope.and(node.eq(Integer(value.term)))
32
42
  rescue ArgumentError
@@ -11,9 +11,10 @@ module SearchableBy
11
11
  super
12
12
  end
13
13
 
14
- def searchable_by(max_terms: nil, **options, &block)
14
+ def searchable_by(max_terms: nil, min_length: 0, **options, &block)
15
15
  _searchable_by_config.instance_eval(&block)
16
16
  _searchable_by_config.max_terms = max_terms if max_terms
17
+ _searchable_by_config.min_length = min_length
17
18
  _searchable_by_config.options.update(options) unless options.empty?
18
19
  _searchable_by_config
19
20
  end
@@ -21,10 +22,11 @@ module SearchableBy
21
22
  # @param [String] query the search query
22
23
  # @return [ActiveRecord::Relation] the scoped relation
23
24
  def search_by(query)
24
- columns = _searchable_by_config.columns
25
+ config = _searchable_by_config
26
+ columns = config.columns
25
27
  return all if columns.empty?
26
28
 
27
- values = Util.norm_values(query).first(_searchable_by_config.max_terms)
29
+ values = Util.norm_values(query, min_length: config.min_length).first(config.max_terms)
28
30
  return all if values.empty?
29
31
 
30
32
  columns.each do |col|
@@ -33,7 +35,7 @@ module SearchableBy
33
35
  clauses = Util.build_clauses(columns, values)
34
36
  return all if clauses.empty?
35
37
 
36
- scope = instance_exec(&_searchable_by_config.scoping)
38
+ scope = instance_exec(&config.scoping)
37
39
  clauses.each do |clause|
38
40
  scope = scope.where(clause)
39
41
  end
@@ -1,11 +1,12 @@
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
@@ -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,22 @@ module SearchableBy
28
28
  end
29
29
 
30
30
  def self.build_clauses(columns, values)
31
- clauses = values.map do |value|
32
- grouping = columns.map do |column|
33
- column.build_condition(value)
31
+ values.map do |value|
32
+ # TODO: remove when removing min_length option from columns
33
+ usable = columns.all? do |column|
34
+ column.send(:usable?, value)
34
35
  end
35
- grouping.compact!
36
- next if grouping.empty?
36
+ next unless usable
37
+
38
+ group = columns.map do |column|
39
+ column.build_condition(value)
40
+ end.tap(&:compact!)
41
+ next if group.empty?
37
42
 
38
- clause = grouping.inject(&:or)
43
+ clause = group.inject(&:or)
39
44
  clause = clause.not if value.negate
40
45
  clause
41
- end
42
- clauses.compact!
43
- clauses
46
+ end.tap(&:compact!)
44
47
  end
45
48
  end
46
49
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'searchable-by'
3
- s.version = '0.5.9'
3
+ s.version = '0.6.0'
4
4
  s.authors = ['Dimitrij Denissenko']
5
5
  s.email = ['dimitrij@blacksquaremedia.com']
6
6
  s.summary = 'Generate search scopes'
@@ -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
@@ -67,9 +67,16 @@ describe SearchableBy do
67
67
  expect(Post.search_by('recip').pluck(:title)).to match_array(%w[ax1 ax2 bx1 bx2 ab1])
68
68
  end
69
69
 
70
- it 'supports min term length' do
71
- expect(User.search_by('+ir')).to be_empty
72
- expect(User.search_by('irs')).to match_array([USERS[:a]])
70
+ it 'supports min term length in context' do
71
+ # values are discarded - too short for bio
72
+ expect(User.search_by('+be')).to match_array(USERS.values_at(:a, :b))
73
+ expect(User.search_by('is')).to match_array(USERS.values_at(:a, :b))
74
+
75
+ # value is used to scope
76
+ expect(User.search_by('ear')).to match_array(USERS.values_at(:a))
77
+
78
+ # one used, one discarded
79
+ expect(User.search_by('beard is')).to match_array(USERS.values_at(:a))
73
80
  end
74
81
 
75
82
  it 'searches within scopes' do
@@ -82,8 +89,8 @@ describe SearchableBy do
82
89
  end
83
90
 
84
91
  it 'supports wildcard searching' do
85
- expect(User.search_by('uni*dom')).to match_array([USERS[:a]])
86
- expect(User.search_by('uni*o')).to match_array([USERS[:a], USERS[:b]])
92
+ expect(User.search_by('uni*dom')).to match_array(USERS.values_at(:a))
93
+ expect(User.search_by('uni*o')).to match_array(USERS.values_at(:a, :b))
87
94
  expect(User.search_by('uni*of*dom')).to be_empty
88
95
  end
89
96
  end
data/spec/spec_helper.rb CHANGED
@@ -30,8 +30,8 @@ end
30
30
  class User < AbstractModel
31
31
  has_many :posts, foreign_key: :author_id
32
32
 
33
- searchable_by do
34
- column :bio, min_length: 3
33
+ searchable_by min_length: 3 do
34
+ column :bio
35
35
  column :country, wildcard: '*'
36
36
  end
37
37
  end
@@ -53,8 +53,8 @@ class Post < AbstractModel
53
53
  end
54
54
 
55
55
  USERS = {
56
- a: User.create!(name: 'Alice', bio: 'First user', country: 'United Kingdom'),
57
- b: User.create!(name: 'Bob', bio: 'Second user', country: 'United States of America'),
56
+ a: User.create!(name: 'Alice', bio: "Chuck Norris' beard is immutable.", country: 'United Kingdom'),
57
+ b: User.create!(name: 'Bob', bio: 'Chuck Norris can divide by zero.', country: 'United States of America'),
58
58
  }.freeze
59
59
 
60
60
  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.5.9
4
+ version: 0.6.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-04-27 00:00:00.000000000 Z
11
+ date: 2021-07-12 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,7 +115,7 @@ executables: []
129
115
  extensions: []
130
116
  extra_rdoc_files: []
131
117
  files:
132
- - ".github/workflows/ruby.yml"
118
+ - ".github/workflows/test.yml"
133
119
  - ".gitignore"
134
120
  - ".rubocop.yml"
135
121
  - Gemfile
@@ -166,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
152
  - !ruby/object:Gem::Version
167
153
  version: '0'
168
154
  requirements: []
169
- rubygems_version: 3.1.4
155
+ rubygems_version: 3.2.15
170
156
  signing_key:
171
157
  specification_version: 4
172
158
  summary: Generate search scopes