searchable-by 0.5.5 → 0.5.6
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/Gemfile.lock +26 -24
- data/LICENSE +1 -1
- data/README.md +2 -1
- data/lib/searchable_by.rb +8 -145
- data/lib/searchable_by/column.rb +34 -0
- data/lib/searchable_by/concern.rb +43 -0
- data/lib/searchable_by/config.rb +31 -0
- data/lib/searchable_by/util.rb +46 -0
- data/searchable-by.gemspec +1 -1
- data/spec/searchable_by/util_spec.rb +35 -0
- data/spec/searchable_by_spec.rb +12 -33
- data/spec/spec_helper.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e2f1b8d0d05abf263dc93c1bd81ab818a88eedf135ff10cd5414d54d3ae17a1
|
|
4
|
+
data.tar.gz: 04d1e040390dcf8c77adbc1bf5bd491505373147798b1c95e4e4cc6c259daab5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6bcafc69897951760eba1a9fb049664aa3360536525d31e6f5b1d9094ce274cc82c6936aa69e19b8ece975e1f74d42f424646d5a6e4de4bc4c12b29b24a4b33
|
|
7
|
+
data.tar.gz: a5762fcbf915002a781e6d1996cae4df2ce80fc62d0948c0cb5965c97b07d0d5a9030cb7acf360bba4482a4f05026f1e082254f5f545a8fa5ba1bdb946ef520e
|
data/Gemfile.lock
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
searchable-by (0.5.
|
|
4
|
+
searchable-by (0.5.6)
|
|
5
5
|
activerecord
|
|
6
6
|
activesupport
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: http://rubygems.org/
|
|
10
10
|
specs:
|
|
11
|
-
activemodel (6.0.3.
|
|
12
|
-
activesupport (= 6.0.3.
|
|
13
|
-
activerecord (6.0.3.
|
|
14
|
-
activemodel (= 6.0.3.
|
|
15
|
-
activesupport (= 6.0.3.
|
|
16
|
-
activesupport (6.0.3.
|
|
11
|
+
activemodel (6.0.3.3)
|
|
12
|
+
activesupport (= 6.0.3.3)
|
|
13
|
+
activerecord (6.0.3.3)
|
|
14
|
+
activemodel (= 6.0.3.3)
|
|
15
|
+
activesupport (= 6.0.3.3)
|
|
16
|
+
activesupport (6.0.3.3)
|
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
18
|
i18n (>= 0.7, < 2)
|
|
19
19
|
minitest (~> 5.1)
|
|
20
20
|
tzinfo (~> 1.1)
|
|
21
21
|
zeitwerk (~> 2.2, >= 2.2.2)
|
|
22
|
-
ast (2.4.
|
|
23
|
-
concurrent-ruby (1.1.
|
|
24
|
-
diff-lcs (1.
|
|
25
|
-
i18n (1.8.
|
|
22
|
+
ast (2.4.1)
|
|
23
|
+
concurrent-ruby (1.1.7)
|
|
24
|
+
diff-lcs (1.4.4)
|
|
25
|
+
i18n (1.8.5)
|
|
26
26
|
concurrent-ruby (~> 1.0)
|
|
27
|
-
minitest (5.14.
|
|
28
|
-
parallel (1.19.
|
|
29
|
-
parser (2.7.1.
|
|
30
|
-
ast (~> 2.4.
|
|
27
|
+
minitest (5.14.2)
|
|
28
|
+
parallel (1.19.2)
|
|
29
|
+
parser (2.7.1.4)
|
|
30
|
+
ast (~> 2.4.1)
|
|
31
31
|
rainbow (3.0.0)
|
|
32
32
|
rake (13.0.1)
|
|
33
|
+
regexp_parser (1.7.1)
|
|
33
34
|
rexml (3.2.4)
|
|
34
35
|
rspec (3.9.0)
|
|
35
36
|
rspec-core (~> 3.9.0)
|
|
@@ -44,25 +45,26 @@ GEM
|
|
|
44
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
45
46
|
rspec-support (~> 3.9.0)
|
|
46
47
|
rspec-support (3.9.3)
|
|
47
|
-
rubocop (0.
|
|
48
|
+
rubocop (0.91.0)
|
|
48
49
|
parallel (~> 1.10)
|
|
49
|
-
parser (>= 2.7.
|
|
50
|
+
parser (>= 2.7.1.1)
|
|
50
51
|
rainbow (>= 2.2.2, < 4.0)
|
|
52
|
+
regexp_parser (>= 1.7)
|
|
51
53
|
rexml
|
|
52
|
-
rubocop-ast (>= 0.0.
|
|
54
|
+
rubocop-ast (>= 0.4.0, < 1.0)
|
|
53
55
|
ruby-progressbar (~> 1.7)
|
|
54
56
|
unicode-display_width (>= 1.4.0, < 2.0)
|
|
55
|
-
rubocop-ast (0.
|
|
56
|
-
parser (>= 2.7.
|
|
57
|
-
rubocop-performance (1.
|
|
58
|
-
rubocop (>= 0.
|
|
57
|
+
rubocop-ast (0.4.1)
|
|
58
|
+
parser (>= 2.7.1.4)
|
|
59
|
+
rubocop-performance (1.8.0)
|
|
60
|
+
rubocop (>= 0.87.0)
|
|
59
61
|
ruby-progressbar (1.10.1)
|
|
60
62
|
sqlite3 (1.4.2)
|
|
61
63
|
thread_safe (0.3.6)
|
|
62
64
|
tzinfo (1.2.7)
|
|
63
65
|
thread_safe (~> 0.1)
|
|
64
66
|
unicode-display_width (1.7.0)
|
|
65
|
-
zeitwerk (2.
|
|
67
|
+
zeitwerk (2.4.0)
|
|
66
68
|
|
|
67
69
|
PLATFORMS
|
|
68
70
|
ruby
|
|
@@ -77,4 +79,4 @@ DEPENDENCIES
|
|
|
77
79
|
sqlite3
|
|
78
80
|
|
|
79
81
|
BUNDLED WITH
|
|
80
|
-
2.1.
|
|
82
|
+
2.1.4
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -15,7 +15,8 @@ class Post < ActiveRecord::Base
|
|
|
15
15
|
# Limit the number of terms per query to 3.
|
|
16
16
|
searchable_by max_terms: 3 do
|
|
17
17
|
# Allow to search strings.
|
|
18
|
-
|
|
18
|
+
# Use btree index-friendly prefix match, e.g. `ILIKE 'term%'` instead of default `ILIKE '%term%'`.
|
|
19
|
+
column :title, match: :prefix
|
|
19
20
|
|
|
20
21
|
# ... and integers.
|
|
21
22
|
column :id, type: :integer
|
data/lib/searchable_by.rb
CHANGED
|
@@ -1,149 +1,12 @@
|
|
|
1
1
|
require 'active_record'
|
|
2
2
|
|
|
3
|
-
module
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
module SearchableBy
|
|
4
|
+
autoload :Column, 'searchable_by/column'
|
|
5
|
+
autoload :Concern, 'searchable_by/concern'
|
|
6
|
+
autoload :Config, 'searchable_by/config'
|
|
7
|
+
autoload :Util, 'searchable_by/util'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
@attr = attr
|
|
11
|
-
@type = type.to_sym
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
class Config
|
|
16
|
-
attr_reader :columns, :scoping
|
|
17
|
-
attr_accessor :max_terms
|
|
18
|
-
|
|
19
|
-
def initialize
|
|
20
|
-
@columns = []
|
|
21
|
-
@max_terms = 5
|
|
22
|
-
scope { all }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def initialize_copy(other)
|
|
26
|
-
@columns = other.columns.dup
|
|
27
|
-
super
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def column(*attrs, &block)
|
|
31
|
-
opts = attrs.extract_options!
|
|
32
|
-
attrs.each do |attr|
|
|
33
|
-
columns.push Column.new(attr, **opts)
|
|
34
|
-
end
|
|
35
|
-
columns.push Column.new(block, **opts) if block
|
|
36
|
-
columns
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def scope(&block)
|
|
40
|
-
@scoping = block
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
Value = Struct.new(:term, :negate)
|
|
45
|
-
|
|
46
|
-
def self.norm_values(query)
|
|
47
|
-
values = []
|
|
48
|
-
query = query.to_s.dup
|
|
49
|
-
|
|
50
|
-
# capture any terms inside double quotes
|
|
51
|
-
# exclude from seach if preceded by '-'
|
|
52
|
-
query.gsub!(/([\-\+]?)"+([^"]*)"+/) do |_|
|
|
53
|
-
term = Regexp.last_match(2)
|
|
54
|
-
negate = Regexp.last_match(1) == '-'
|
|
55
|
-
|
|
56
|
-
values.push Value.new(term, negate) unless term.blank?
|
|
57
|
-
''
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# for the remaining terms remove sign if precedes
|
|
61
|
-
# exclude term from search if sign preceding is '-'
|
|
62
|
-
query.split(' ').each do |term|
|
|
63
|
-
negate = term[0] == '-'
|
|
64
|
-
term.slice!(0) if negate || term[0] == '+'
|
|
65
|
-
|
|
66
|
-
values.push Value.new(term, negate) unless term.blank?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
values.uniq!
|
|
70
|
-
values
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def self.build_clauses(columns, values)
|
|
74
|
-
clauses = values.map do |value|
|
|
75
|
-
grouping = columns.map do |column|
|
|
76
|
-
build_condition(column, value)
|
|
77
|
-
end
|
|
78
|
-
grouping.compact!
|
|
79
|
-
next if grouping.empty?
|
|
80
|
-
|
|
81
|
-
clause = grouping.inject(&:or)
|
|
82
|
-
clause = clause.not if value.negate
|
|
83
|
-
clause
|
|
84
|
-
end
|
|
85
|
-
clauses.compact!
|
|
86
|
-
clauses
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def self.build_condition(column, value)
|
|
90
|
-
case column.type
|
|
91
|
-
when :int, :integer
|
|
92
|
-
begin
|
|
93
|
-
column.node.not_eq(nil).and(column.node.eq(Integer(value.term)))
|
|
94
|
-
rescue ArgumentError
|
|
95
|
-
nil
|
|
96
|
-
end
|
|
97
|
-
else
|
|
98
|
-
term = value.term.dup
|
|
99
|
-
term.gsub!('%', '\%')
|
|
100
|
-
term.gsub!('_', '\_')
|
|
101
|
-
column.node.not_eq(nil).and(column.node.matches("%#{term}%"))
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
module ClassMethods
|
|
106
|
-
def self.extended(base) # :nodoc:
|
|
107
|
-
base.class_attribute :_searchable_by_config, instance_accessor: false, instance_predicate: false
|
|
108
|
-
base._searchable_by_config = Config.new
|
|
109
|
-
super
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def inherited(base) # :nodoc:
|
|
113
|
-
base._searchable_by_config = _searchable_by_config.dup
|
|
114
|
-
super
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def searchable_by(max_terms: 5, &block)
|
|
118
|
-
_searchable_by_config.instance_eval(&block)
|
|
119
|
-
_searchable_by_config.max_terms = max_terms if max_terms
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# @param [String] query the search query
|
|
123
|
-
# @return [ActiveRecord::Relation] the scoped relation
|
|
124
|
-
def search_by(query)
|
|
125
|
-
columns = _searchable_by_config.columns
|
|
126
|
-
return all if columns.empty?
|
|
127
|
-
|
|
128
|
-
values = SearchableBy.norm_values(query).first(_searchable_by_config.max_terms)
|
|
129
|
-
return all if values.empty?
|
|
130
|
-
|
|
131
|
-
columns.each do |col|
|
|
132
|
-
col.node ||= col.attr.is_a?(Proc) ? col.attr.call : arel_table[col.attr]
|
|
133
|
-
end
|
|
134
|
-
clauses = SearchableBy.build_clauses(columns, values)
|
|
135
|
-
return all if clauses.empty?
|
|
136
|
-
|
|
137
|
-
scope = instance_exec(&_searchable_by_config.scoping)
|
|
138
|
-
clauses.each do |clause|
|
|
139
|
-
scope = scope.where(clause)
|
|
140
|
-
end
|
|
141
|
-
scope
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
class Base
|
|
147
|
-
extend SearchableBy::ClassMethods
|
|
148
|
-
end
|
|
9
|
+
Value = Struct.new(:term, :negate)
|
|
149
10
|
end
|
|
11
|
+
|
|
12
|
+
ActiveRecord::Base.extend SearchableBy::Concern if defined?(::ActiveRecord::Base)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SearchableBy
|
|
2
|
+
class Column
|
|
3
|
+
attr_reader :attr, :type, :match
|
|
4
|
+
attr_accessor :node
|
|
5
|
+
|
|
6
|
+
def initialize(attr, type: :string, match: :all)
|
|
7
|
+
@attr = attr
|
|
8
|
+
@type = type.to_sym
|
|
9
|
+
@match = match
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build_condition(value)
|
|
13
|
+
case type
|
|
14
|
+
when :int, :integer
|
|
15
|
+
begin
|
|
16
|
+
node.not_eq(nil).and(node.eq(Integer(value.term)))
|
|
17
|
+
rescue ArgumentError
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
term = value.term.dup
|
|
22
|
+
term.gsub!('%', '\%')
|
|
23
|
+
term.gsub!('_', '\_')
|
|
24
|
+
case match
|
|
25
|
+
when :prefix
|
|
26
|
+
term << '%'
|
|
27
|
+
else
|
|
28
|
+
term = "%#{term}%"
|
|
29
|
+
end
|
|
30
|
+
node.not_eq(nil).and(node.matches(term))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module SearchableBy
|
|
2
|
+
module Concern
|
|
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
|
|
6
|
+
super
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def inherited(base) # :nodoc:
|
|
10
|
+
base._searchable_by_config = _searchable_by_config.dup
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def searchable_by(max_terms: nil, **options, &block)
|
|
15
|
+
_searchable_by_config.instance_eval(&block)
|
|
16
|
+
_searchable_by_config.max_terms = max_terms if max_terms
|
|
17
|
+
_searchable_by_config.options.update(options) unless options.empty?
|
|
18
|
+
_searchable_by_config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [String] query the search query
|
|
22
|
+
# @return [ActiveRecord::Relation] the scoped relation
|
|
23
|
+
def search_by(query)
|
|
24
|
+
columns = _searchable_by_config.columns
|
|
25
|
+
return all if columns.empty?
|
|
26
|
+
|
|
27
|
+
values = Util.norm_values(query).first(_searchable_by_config.max_terms)
|
|
28
|
+
return all if values.empty?
|
|
29
|
+
|
|
30
|
+
columns.each do |col|
|
|
31
|
+
col.node ||= col.attr.is_a?(Proc) ? col.attr.call : arel_table[col.attr]
|
|
32
|
+
end
|
|
33
|
+
clauses = Util.build_clauses(columns, values)
|
|
34
|
+
return all if clauses.empty?
|
|
35
|
+
|
|
36
|
+
scope = instance_exec(&_searchable_by_config.scoping)
|
|
37
|
+
clauses.each do |clause|
|
|
38
|
+
scope = scope.where(clause)
|
|
39
|
+
end
|
|
40
|
+
scope
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SearchableBy
|
|
2
|
+
class Config
|
|
3
|
+
attr_reader :columns, :scoping, :options
|
|
4
|
+
attr_accessor :max_terms
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@columns = []
|
|
8
|
+
@max_terms = 5
|
|
9
|
+
@options = {}
|
|
10
|
+
scope { all }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize_copy(other)
|
|
14
|
+
@columns = other.columns.dup
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def column(*attrs, &block)
|
|
19
|
+
opts = attrs.extract_options!
|
|
20
|
+
attrs.each do |attr|
|
|
21
|
+
columns.push Column.new(attr, **@options, **opts)
|
|
22
|
+
end
|
|
23
|
+
columns.push Column.new(block, **@options, **opts) if block
|
|
24
|
+
columns
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def scope(&block)
|
|
28
|
+
@scoping = block
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module SearchableBy
|
|
2
|
+
module Util
|
|
3
|
+
def self.norm_values(query)
|
|
4
|
+
values = []
|
|
5
|
+
query = query.to_s.dup
|
|
6
|
+
|
|
7
|
+
# capture any terms inside double quotes
|
|
8
|
+
# exclude from search if preceded by '-'
|
|
9
|
+
query.gsub!(/([\-+]?)"+([^"]*)"+/) do |_|
|
|
10
|
+
term = Regexp.last_match(2)
|
|
11
|
+
negate = Regexp.last_match(1) == '-'
|
|
12
|
+
|
|
13
|
+
values.push Value.new(term, negate) unless term.blank?
|
|
14
|
+
''
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# for the remaining terms remove sign if precedes
|
|
18
|
+
# exclude term from search if sign preceding is '-'
|
|
19
|
+
query.split(' ').each do |term|
|
|
20
|
+
negate = term[0] == '-'
|
|
21
|
+
term.slice!(0) if negate || term[0] == '+'
|
|
22
|
+
|
|
23
|
+
values.push Value.new(term, negate) unless term.blank?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
values.uniq!
|
|
27
|
+
values
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.build_clauses(columns, values)
|
|
31
|
+
clauses = values.map do |value|
|
|
32
|
+
grouping = columns.map do |column|
|
|
33
|
+
column.build_condition(value)
|
|
34
|
+
end
|
|
35
|
+
grouping.compact!
|
|
36
|
+
next if grouping.empty?
|
|
37
|
+
|
|
38
|
+
clause = grouping.inject(&:or)
|
|
39
|
+
clause = clause.not if value.negate
|
|
40
|
+
clause
|
|
41
|
+
end
|
|
42
|
+
clauses.compact!
|
|
43
|
+
clauses
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/searchable-by.gemspec
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe SearchableBy::Util do
|
|
4
|
+
context 'norm_values' do
|
|
5
|
+
def norm(str)
|
|
6
|
+
described_class.norm_values(str).each_with_object({}) do |val, acc|
|
|
7
|
+
acc[val.term] = val.negate
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'should tokenise strings' do
|
|
12
|
+
expect(norm(nil)).to eq({})
|
|
13
|
+
expect(norm('""')).to eq({})
|
|
14
|
+
expect(norm('-+""')).to eq({})
|
|
15
|
+
expect(norm('simple words')).to eq('simple' => false, 'words' => false)
|
|
16
|
+
expect(norm(" with \t spaces\n")).to eq('with' => false, 'spaces' => false)
|
|
17
|
+
expect(norm('with with duplicates with')).to eq('with' => false, 'duplicates' => false)
|
|
18
|
+
expect(norm('with "full term"')).to eq('full term' => false, 'with' => false)
|
|
19
|
+
expect(norm('"""odd double quotes around"""')).to eq('odd double quotes around' => false)
|
|
20
|
+
expect(norm('""even double quotes around""')).to eq('even double quotes around'=> false)
|
|
21
|
+
expect(norm('with\'apostrophe')).to eq("with'apostrophe" => false)
|
|
22
|
+
expect(norm('with -minus')).to eq('minus' => true, 'with' => false)
|
|
23
|
+
expect(norm('with +plus')).to eq('plus' => false, 'with' => false)
|
|
24
|
+
expect(norm('with-minus')).to eq('with-minus' => false)
|
|
25
|
+
expect(norm('with+plus')).to eq('with+plus' => false)
|
|
26
|
+
expect(norm('with -"minus before"')).to eq('minus before' => true, 'with' => false)
|
|
27
|
+
expect(norm('with "-minus within"')).to eq('-minus within' => false, 'with' => false)
|
|
28
|
+
expect(norm('with +"plus before"')).to eq('plus before' => false, 'with' => false)
|
|
29
|
+
expect(norm('with "+plus within"')).to eq('+plus within' => false, 'with' => false)
|
|
30
|
+
expect(norm('+plus "in other term"')).to eq('in other term' => false, 'plus' => false)
|
|
31
|
+
expect(norm('with_blank \'\'')).to eq('with_blank' => false, '\'\'' => false)
|
|
32
|
+
expect(norm('with_blank_doubles ""')).to eq('with_blank_doubles' => false)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/spec/searchable_by_spec.rb
CHANGED
|
@@ -1,38 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe
|
|
4
|
-
context 'norm_values' do
|
|
5
|
-
def norm(str)
|
|
6
|
-
described_class.norm_values(str).each_with_object({}) do |val, acc|
|
|
7
|
-
acc[val.term] = val.negate
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'should tokenise strings' do
|
|
12
|
-
expect(norm(nil)).to eq({})
|
|
13
|
-
expect(norm('""')).to eq({})
|
|
14
|
-
expect(norm('-+""')).to eq({})
|
|
15
|
-
expect(norm('simple words')).to eq('simple' => false, 'words' => false)
|
|
16
|
-
expect(norm(" with \t spaces\n")).to eq('with' => false, 'spaces' => false)
|
|
17
|
-
expect(norm('with with duplicates with')).to eq('with' => false, 'duplicates' => false)
|
|
18
|
-
expect(norm('with "full term"')).to eq('full term' => false, 'with' => false)
|
|
19
|
-
expect(norm('"""odd double quotes around"""')).to eq('odd double quotes around' => false)
|
|
20
|
-
expect(norm('""even double quotes around""')).to eq('even double quotes around'=> false)
|
|
21
|
-
expect(norm('with\'apostrophe')).to eq("with'apostrophe" => false)
|
|
22
|
-
expect(norm('with -minus')).to eq('minus' => true, 'with' => false)
|
|
23
|
-
expect(norm('with +plus')).to eq('plus' => false, 'with' => false)
|
|
24
|
-
expect(norm('with-minus')).to eq('with-minus' => false)
|
|
25
|
-
expect(norm('with+plus')).to eq('with+plus' => false)
|
|
26
|
-
expect(norm('with -"minus before"')).to eq('minus before' => true, 'with' => false)
|
|
27
|
-
expect(norm('with "-minus within"')).to eq('-minus within' => false, 'with' => false)
|
|
28
|
-
expect(norm('with +"plus before"')).to eq('plus before' => false, 'with' => false)
|
|
29
|
-
expect(norm('with "+plus within"')).to eq('+plus within' => false, 'with' => false)
|
|
30
|
-
expect(norm('+plus "in other term"')).to eq('in other term' => false, 'plus' => false)
|
|
31
|
-
expect(norm('with_blank \'\'')).to eq('with_blank' => false, '\'\'' => false)
|
|
32
|
-
expect(norm('with_blank_doubles ""')).to eq('with_blank_doubles' => false)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
3
|
+
describe SearchableBy do
|
|
36
4
|
it 'should ignore bad inputs' do
|
|
37
5
|
expect(Post.search_by(nil).count).to eq(5)
|
|
38
6
|
expect(Post.search_by('').count).to eq(5)
|
|
@@ -69,6 +37,17 @@ describe ActiveRecord::SearchableBy do
|
|
|
69
37
|
expect(Post.search_by('bob +"her recipe"').pluck(:title)).to match_array(%w[b1])
|
|
70
38
|
end
|
|
71
39
|
|
|
40
|
+
it 'should respect match options' do
|
|
41
|
+
# name uses match: :prefix
|
|
42
|
+
expect(Post.search_by('alice').pluck(:title)).to match_array(%w[a1 a2 ab])
|
|
43
|
+
expect(Post.search_by('ali').pluck(:title)).to match_array(%w[a1 a2 ab])
|
|
44
|
+
expect(Post.search_by('lice').pluck(:title)).to be_empty
|
|
45
|
+
expect(Post.search_by('li').pluck(:title)).to be_empty
|
|
46
|
+
|
|
47
|
+
# title uses match: :all (default)
|
|
48
|
+
expect(Post.search_by('recip').pluck(:title)).to match_array(%w[a1 a2 b1 b2 ab])
|
|
49
|
+
end
|
|
50
|
+
|
|
72
51
|
it 'should search within scopes' do
|
|
73
52
|
expect(Post.where(title: 'a1').search_by('ALICE').pluck(:title)).to match_array(%w[a1])
|
|
74
53
|
expect(Post.where(title: 'a1').search_by('bOb').pluck(:title)).to match_array(%w[])
|
data/spec/spec_helper.rb
CHANGED
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.
|
|
4
|
+
version: 0.5.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dimitrij Denissenko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -139,7 +139,12 @@ files:
|
|
|
139
139
|
- Rakefile
|
|
140
140
|
- lib/searchable-by.rb
|
|
141
141
|
- lib/searchable_by.rb
|
|
142
|
+
- lib/searchable_by/column.rb
|
|
143
|
+
- lib/searchable_by/concern.rb
|
|
144
|
+
- lib/searchable_by/config.rb
|
|
145
|
+
- lib/searchable_by/util.rb
|
|
142
146
|
- searchable-by.gemspec
|
|
147
|
+
- spec/searchable_by/util_spec.rb
|
|
143
148
|
- spec/searchable_by_spec.rb
|
|
144
149
|
- spec/spec_helper.rb
|
|
145
150
|
homepage: https://github.com/bsm/sortable-by
|
|
@@ -166,5 +171,6 @@ signing_key:
|
|
|
166
171
|
specification_version: 4
|
|
167
172
|
summary: Generate search scopes
|
|
168
173
|
test_files:
|
|
174
|
+
- spec/searchable_by/util_spec.rb
|
|
169
175
|
- spec/searchable_by_spec.rb
|
|
170
176
|
- spec/spec_helper.rb
|