searchable-by 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 472133b9b419e573a1c87b721a5c368b3915ef28cb94e897424f55f7191f723f
4
- data.tar.gz: d3c31e307f54b0545171e7e12b86d75d32f5874676af467399fdb2e533927c16
3
+ metadata.gz: e3c6177e1001a078408b9466190f08df81a8dc812724903650ecd4a1b267fa09
4
+ data.tar.gz: 8c08713658466f75b21cac681a4dcce20bef6dc1544c4d7a935bb1eb285f54ea
5
5
  SHA512:
6
- metadata.gz: 1bd8dc39da51b94327d7912661cda9e2d38cdbf7d6e62eea1930e5d39a85c3bfd81c468477e8cfac48451b7dde055b9075a03686787c481c1cf2fcc5fa9d9c5f
7
- data.tar.gz: e9881b0f0022a0f884d6780afb3f24daf53872a473e22571f1eed730c8c76083148dbec4e99825875a68bb425568776c04fb7ac5d7ad26b1aa9202be3160d357
6
+ metadata.gz: f384f6380a8124c6a8a5f1591d54b14cbce6def8766cbe0271f43f023c98d0a6622f99f0ac22d7537b497ea59ea10ff7412b005729159d6d3cbc78908c00e8a6
7
+ data.tar.gz: ae93c1ce199d6a6315d579346ddff7c04dd1673782c85c130d9c17def56aa94d1873cb44333a53937d789e76683436641fd4187f1b26fd89327079d9857aefe7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- searchable-by (0.6.1)
4
+ searchable-by (0.7.0)
5
5
  activerecord
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -34,6 +34,14 @@ class Post < ActiveRecord::Base
34
34
  joins(:author)
35
35
  end
36
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
37
45
  end
38
46
 
39
47
  # Search for 'alice'
@@ -44,4 +52,7 @@ Post.search_by('alice "pie recipe"')
44
52
 
45
53
  # Search for 'alice' but NOT for 'pie recipe'
46
54
  Post.search_by('alice -"pie recipe"')
55
+
56
+ # Search using metadata profile
57
+ Post.search_by('meta', profile: :metadata)
47
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)
@@ -1,22 +1,19 @@
1
1
  module SearchableBy
2
2
  class Column
3
+ VALID_MATCH_TYPES = %i[all prefix exact].freeze
4
+
3
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, 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
-
8
+ def initialize(attr, type: :string, match: :all, match_phrase: nil, wildcard: nil)
14
9
  @attr = attr
15
10
  @type = type.to_sym
16
11
  @match = match
17
12
  @match_phrase = match_phrase || match
18
- @min_length = opts[:min_length].to_i
19
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
20
17
  end
21
18
 
22
19
  def build_condition(value)
@@ -32,11 +29,6 @@ module SearchableBy
32
29
 
33
30
  private
34
31
 
35
- # TODO: remove when removing min_length option from columns
36
- def usable?(value)
37
- value.term.length >= @min_length
38
- end
39
-
40
32
  def int_condition(scope, value)
41
33
  scope.and(node.eq(Integer(value.term)))
42
34
  rescue ArgumentError
@@ -49,11 +41,13 @@ module SearchableBy
49
41
 
50
42
  case type
51
43
  when :exact
52
- term.downcase!
53
- scope.and(node.lower.eq(term))
54
- when :full
55
- escape_term!(term)
56
- scope.and(node.matches(term))
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
57
51
  when :prefix
58
52
  escape_term!(term)
59
53
  scope.and(node.matches("#{term}%"))
@@ -1,28 +1,25 @@
1
1
  module SearchableBy
2
2
  module Concern
3
3
  def self.extended(base) # :nodoc:
4
- base.class_attribute :_searchable_by_config, instance_accessor: false, instance_predicate: false
5
- base._searchable_by_config = Config.new
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._searchable_by_config = _searchable_by_config.dup
10
+ base._searchable_by_profiles = _searchable_by_profiles.dup
11
11
  super
12
12
  end
13
13
 
14
- def searchable_by(max_terms: nil, min_length: 0, **options, &block)
15
- _searchable_by_config.instance_eval(&block)
16
- _searchable_by_config.max_terms = max_terms if max_terms
17
- _searchable_by_config.min_length = min_length
18
- _searchable_by_config.options.update(options) unless options.empty?
19
- _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
20
17
  end
21
18
 
22
19
  # @param [String] query the search query
23
20
  # @return [ActiveRecord::Relation] the scoped relation
24
- def search_by(query)
25
- config = _searchable_by_config
21
+ def search_by(query, profile: :default)
22
+ config = _searchable_by_profiles[profile]
26
23
  columns = config.columns
27
24
  return all if columns.empty?
28
25
 
@@ -11,6 +11,13 @@ module SearchableBy
11
11
  scope { all }
12
12
  end
13
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
+
14
21
  def initialize_copy(other)
15
22
  @columns = other.columns.dup
16
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
@@ -29,12 +29,6 @@ module SearchableBy
29
29
 
30
30
  def self.build_clauses(columns, values)
31
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)
35
- end
36
- next unless usable
37
-
38
32
  group = columns.map do |column|
39
33
  column.build_condition(value)
40
34
  end.tap(&:compact!)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'searchable-by'
3
- s.version = '0.6.1'
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,8 +7,8 @@ describe SearchableBy do
7
7
  end
8
8
 
9
9
  it 'configures correctly' do
10
- expect(AbstractModel._searchable_by_config.columns.size).to eq(1)
11
- expect(Post._searchable_by_config.columns.size).to eq(6)
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
@@ -93,9 +93,14 @@ describe SearchableBy do
93
93
  expect(Post.search_by(POSTS[:ab1].id.to_s).count).to eq(1)
94
94
  end
95
95
 
96
- it 'supports wildcard searching' do
96
+ it 'supports wildcards' do
97
97
  expect(User.search_by('*uni*dom')).to match_array(USERS.values_at(:a))
98
98
  expect(User.search_by('*uni*o*')).to match_array(USERS.values_at(:a, :b))
99
99
  expect(User.search_by('*uni*of*dom')).to be_empty
100
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
101
106
  end
data/spec/spec_helper.rb CHANGED
@@ -32,7 +32,11 @@ class User < AbstractModel
32
32
 
33
33
  searchable_by min_length: 3 do
34
34
  column :bio
35
- column :country, wildcard: '*', match: :full
35
+ column :country, wildcard: '*', match: :exact
36
+ end
37
+
38
+ searchable_by :country_only do
39
+ column :country
36
40
  end
37
41
  end
38
42
 
@@ -44,7 +48,7 @@ class Post < AbstractModel
44
48
  column :title, match: :prefix, match_phrase: :exact
45
49
  column :body
46
50
  column proc { User.arel_table[:name] }, match: :exact
47
- column proc { User.arel_table[:country] }, wildcard: '*', match: :full
51
+ column proc { User.arel_table[:country] }, wildcard: '*', match: :exact
48
52
  column { User.arel_table.alias('reviewers_posts')[:name] }
49
53
 
50
54
  scope do
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.6.1
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-07-30 00:00:00.000000000 Z
11
+ date: 2021-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -128,6 +128,7 @@ files:
128
128
  - lib/searchable_by/column.rb
129
129
  - lib/searchable_by/concern.rb
130
130
  - lib/searchable_by/config.rb
131
+ - lib/searchable_by/profiles.rb
131
132
  - lib/searchable_by/util.rb
132
133
  - searchable-by.gemspec
133
134
  - spec/searchable_by/util_spec.rb