redi_search 0.1.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/.gitignore +11 -0
- data/.rubocop.yml +1757 -0
- data/.travis.yml +23 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/Rakefile +12 -0
- data/bin/console +8 -0
- data/bin/publish +58 -0
- data/bin/setup +8 -0
- data/bin/test +7 -0
- data/lib/redi_search/client.rb +68 -0
- data/lib/redi_search/configuration.rb +17 -0
- data/lib/redi_search/document/converter.rb +26 -0
- data/lib/redi_search/document.rb +79 -0
- data/lib/redi_search/error.rb +6 -0
- data/lib/redi_search/index.rb +100 -0
- data/lib/redi_search/log_subscriber.rb +94 -0
- data/lib/redi_search/model.rb +57 -0
- data/lib/redi_search/result/collection.rb +22 -0
- data/lib/redi_search/schema/field.rb +21 -0
- data/lib/redi_search/schema/geo_field.rb +30 -0
- data/lib/redi_search/schema/numeric_field.rb +30 -0
- data/lib/redi_search/schema/tag_field.rb +32 -0
- data/lib/redi_search/schema/text_field.rb +36 -0
- data/lib/redi_search/schema.rb +34 -0
- data/lib/redi_search/search/and_clause.rb +15 -0
- data/lib/redi_search/search/boolean_clause.rb +72 -0
- data/lib/redi_search/search/clauses.rb +89 -0
- data/lib/redi_search/search/highlight_clause.rb +43 -0
- data/lib/redi_search/search/or_clause.rb +21 -0
- data/lib/redi_search/search/term.rb +72 -0
- data/lib/redi_search/search/where_clause.rb +66 -0
- data/lib/redi_search/search.rb +89 -0
- data/lib/redi_search/spellcheck.rb +53 -0
- data/lib/redi_search/version.rb +5 -0
- data/lib/redi_search.rb +40 -0
- data/redi_search.gemspec +48 -0
- metadata +141 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/schema"
|
4
|
+
require "redi_search/search"
|
5
|
+
require "redi_search/spellcheck"
|
6
|
+
|
7
|
+
module RediSearch
|
8
|
+
class Index
|
9
|
+
attr_reader :name, :schema, :model
|
10
|
+
|
11
|
+
def initialize(name, schema, model = nil)
|
12
|
+
@name = name
|
13
|
+
@schema = Schema.new(schema)
|
14
|
+
@model = model
|
15
|
+
end
|
16
|
+
|
17
|
+
def search(term = nil, **term_options)
|
18
|
+
Search.new(self, term, model, **term_options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def spellcheck(query, distance: 1)
|
22
|
+
Spellcheck.new(self, query, distance: distance)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create
|
26
|
+
create!
|
27
|
+
rescue Redis::CommandError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def create!
|
32
|
+
client.call!("CREATE", name, "SCHEMA", schema.to_a).ok?
|
33
|
+
end
|
34
|
+
|
35
|
+
def drop
|
36
|
+
drop!
|
37
|
+
rescue Redis::CommandError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def drop!
|
42
|
+
client.call!("DROP", name).ok?
|
43
|
+
end
|
44
|
+
|
45
|
+
def add(record, score = 1.0)
|
46
|
+
add!(record, score)
|
47
|
+
rescue Redis::CommandError
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def add!(record, score = 1.0)
|
52
|
+
client.call!(
|
53
|
+
"ADD", name, record.id, score, "REPLACE", "FIELDS",
|
54
|
+
Document::Converter.new(self, record).document.to_a
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_multiple!(records)
|
59
|
+
client.pipelined do
|
60
|
+
records.each do |record|
|
61
|
+
add!(record)
|
62
|
+
end
|
63
|
+
end.ok?
|
64
|
+
end
|
65
|
+
|
66
|
+
def del(record, delete_document: false)
|
67
|
+
client.call!("DEL", name, record.id, ("DD" if delete_document))
|
68
|
+
end
|
69
|
+
|
70
|
+
def exist?
|
71
|
+
!client.call!("INFO", name).empty?
|
72
|
+
rescue Redis::CommandError
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
def info
|
77
|
+
hash = Hash[*client.call!("INFO", name)]
|
78
|
+
info_struct = Struct.new(*hash.keys.map(&:to_sym))
|
79
|
+
info_struct.new(*hash.values)
|
80
|
+
rescue Redis::CommandError
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def fields
|
85
|
+
@fields ||= schema.fields.map(&:to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
def reindex(docs)
|
89
|
+
drop if exist?
|
90
|
+
create
|
91
|
+
add_multiple! docs
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def client
|
97
|
+
RediSearch.client
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
|
+
def self.runtime=(value)
|
6
|
+
Thread.current[:searchkick_runtime] = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.runtime
|
10
|
+
Thread.current[:searchkick_runtime] ||= 0
|
11
|
+
end
|
12
|
+
|
13
|
+
#:nocov:
|
14
|
+
def self.reset_runtime
|
15
|
+
rt = runtime
|
16
|
+
self.runtime = 0
|
17
|
+
rt
|
18
|
+
end
|
19
|
+
#:nocov:
|
20
|
+
|
21
|
+
def search(event)
|
22
|
+
log_command(event, YELLOW)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(event)
|
26
|
+
log_command(event, GREEN)
|
27
|
+
end
|
28
|
+
|
29
|
+
def drop(event)
|
30
|
+
log_command(event, RED)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(event)
|
34
|
+
log_command(event, GREEN)
|
35
|
+
end
|
36
|
+
|
37
|
+
def info(event)
|
38
|
+
log_command(event, CYAN)
|
39
|
+
end
|
40
|
+
|
41
|
+
def pipeline(event)
|
42
|
+
log_command(event, MAGENTA)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get(event)
|
46
|
+
log_command(event, CYAN)
|
47
|
+
end
|
48
|
+
|
49
|
+
def mget(event)
|
50
|
+
log_command(event, CYAN)
|
51
|
+
end
|
52
|
+
|
53
|
+
def del(event)
|
54
|
+
log_command(event, RED)
|
55
|
+
end
|
56
|
+
|
57
|
+
def spellcheck(event)
|
58
|
+
log_command(event, YELLOW)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def log_command(event, debug_color)
|
64
|
+
self.class.runtime += event.duration
|
65
|
+
return unless logger.debug?
|
66
|
+
|
67
|
+
payload = event.payload
|
68
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
69
|
+
command = command_string(payload)
|
70
|
+
|
71
|
+
debug " #{color(name, RED, true)} #{color(command, debug_color, true)}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def command_string(payload)
|
75
|
+
payload[:query].flatten.map.with_index do |arg, i|
|
76
|
+
arg = "FT.#{arg}" if prepend_ft?(arg, i)
|
77
|
+
arg = arg.inspect if inspect_arg?(payload, arg)
|
78
|
+
arg
|
79
|
+
end.join(" ")
|
80
|
+
end
|
81
|
+
|
82
|
+
def multiword?(string)
|
83
|
+
!string.to_s.starts_with?(/\(-?@/) && string.to_s.split(/\s|\|/).size > 1
|
84
|
+
end
|
85
|
+
|
86
|
+
def prepend_ft?(arg, index)
|
87
|
+
index.zero? && !multiword?(arg)
|
88
|
+
end
|
89
|
+
|
90
|
+
def inspect_arg?(payload, arg)
|
91
|
+
multiword?(arg) && payload[:query].flatten.count > 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/index"
|
4
|
+
require "redi_search/document/converter"
|
5
|
+
|
6
|
+
require "active_support/concern"
|
7
|
+
|
8
|
+
module RediSearch
|
9
|
+
module Model
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
attr_reader :redi_search_index
|
14
|
+
|
15
|
+
def redi_search(**options) # rubocop:disable Metrics/MethodLength
|
16
|
+
@redi_search_index = Index.new(
|
17
|
+
options[:index_name] || "#{name.underscore}_idx",
|
18
|
+
options[:schema],
|
19
|
+
self
|
20
|
+
)
|
21
|
+
|
22
|
+
if respond_to? :after_commit
|
23
|
+
after_commit :redi_search_add_document, on: [:create, :update]
|
24
|
+
end
|
25
|
+
if respond_to? :after_destroy_commit
|
26
|
+
after_destroy_commit :redi_search_delete_document
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def search(term = nil, **term_options)
|
31
|
+
redi_search_index.search(term, **term_options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def reindex
|
35
|
+
redi_search_index.reindex(all)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def redi_search_document
|
42
|
+
Document::Converter.new(self.class.redi_search_index, self).document
|
43
|
+
end
|
44
|
+
|
45
|
+
def redi_search_delete_document
|
46
|
+
return unless self.class.redi_search_index.exist?
|
47
|
+
|
48
|
+
self.class.redi_search_index.del(self, delete_document: true)
|
49
|
+
end
|
50
|
+
|
51
|
+
def redi_search_add_document
|
52
|
+
return unless self.class.redi_search_index.exist?
|
53
|
+
|
54
|
+
self.class.redi_search_index.add(self)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Result
|
5
|
+
class Collection < Array
|
6
|
+
def initialize(index, count, records)
|
7
|
+
@count = count
|
8
|
+
super(Hash[*records].map do |doc_id, fields|
|
9
|
+
Document.new(index, doc_id, Hash[*fields])
|
10
|
+
end)
|
11
|
+
end
|
12
|
+
|
13
|
+
def count
|
14
|
+
@count || super
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@count || super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Schema
|
5
|
+
class Field
|
6
|
+
private
|
7
|
+
|
8
|
+
FALSES = [
|
9
|
+
nil, "", false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
def boolean_options_string
|
13
|
+
boolean_options.map do |option|
|
14
|
+
unless FALSES.include?(send(option))
|
15
|
+
option.to_s.upcase.split("_").join
|
16
|
+
end
|
17
|
+
end.compact
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/schema/field"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Schema
|
7
|
+
class GeoField < Field
|
8
|
+
def initialize(name, sortable: false, no_index: false)
|
9
|
+
@name = name
|
10
|
+
@sortable = sortable
|
11
|
+
@no_index = no_index
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_a
|
15
|
+
query = [name.to_s, "GEO"]
|
16
|
+
query += boolean_options_string
|
17
|
+
|
18
|
+
query
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :name, :sortable, :no_index
|
24
|
+
|
25
|
+
def boolean_options
|
26
|
+
%i(sortable no_index)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/schema/field"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Schema
|
7
|
+
class NumericField < Field
|
8
|
+
def initialize(name, sortable: false, no_index: false)
|
9
|
+
@name = name
|
10
|
+
@sortable = sortable
|
11
|
+
@no_index = no_index
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_a
|
15
|
+
query = [name.to_s, "NUMERIC"]
|
16
|
+
query += boolean_options_string
|
17
|
+
|
18
|
+
query
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :name, :sortable, :no_index
|
24
|
+
|
25
|
+
def boolean_options
|
26
|
+
%i(sortable no_index)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/schema/field"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Schema
|
7
|
+
class TagField < Field
|
8
|
+
def initialize(name, separator: ",", sortable: false, no_index: false)
|
9
|
+
@name = name
|
10
|
+
@separator = separator
|
11
|
+
@sortable = sortable
|
12
|
+
@no_index = no_index
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_a
|
16
|
+
query = [name.to_s, "TAG"]
|
17
|
+
query += boolean_options_string
|
18
|
+
query += ["SEPARATOR", separator] if separator
|
19
|
+
|
20
|
+
query
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :name, :separator, :sortable, :no_index
|
26
|
+
|
27
|
+
def boolean_options
|
28
|
+
%i(sortable no_index)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/type"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Schema
|
7
|
+
class TextField < Field
|
8
|
+
def initialize(name, weight: 1.0, phonetic: nil, sortable: false,
|
9
|
+
no_index: false, no_stem: false)
|
10
|
+
@name = name
|
11
|
+
@weight = weight
|
12
|
+
@phonetic = phonetic
|
13
|
+
@sortable = sortable
|
14
|
+
@no_index = no_index
|
15
|
+
@no_stem = no_stem
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_a
|
19
|
+
query = [name.to_s, "TEXT"]
|
20
|
+
query += boolean_options_string
|
21
|
+
query += ["WEIGHT", weight] if weight
|
22
|
+
query += ["PHONETIC", phonetic] if phonetic
|
23
|
+
|
24
|
+
query
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :name, :weight, :phonetic, :sortable, :no_index, :no_stem
|
30
|
+
|
31
|
+
def boolean_options
|
32
|
+
%i(sortable no_index no_stem)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/schema/geo_field"
|
4
|
+
require "redi_search/schema/numeric_field"
|
5
|
+
require "redi_search/schema/tag_field"
|
6
|
+
require "redi_search/schema/text_field"
|
7
|
+
|
8
|
+
module RediSearch
|
9
|
+
class Schema
|
10
|
+
def initialize(raw)
|
11
|
+
@raw = raw
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_a
|
15
|
+
raw.map do |field_name, options|
|
16
|
+
options = [options] if options.is_a? Symbol
|
17
|
+
schema, options = options.to_a.flatten
|
18
|
+
|
19
|
+
"RediSearch::Schema::#{schema.to_s.capitalize}Field".
|
20
|
+
constantize.
|
21
|
+
new(field_name, **options.to_h).
|
22
|
+
to_a
|
23
|
+
end.flatten
|
24
|
+
end
|
25
|
+
|
26
|
+
def fields
|
27
|
+
raw.keys
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :raw
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
class BooleanClause
|
6
|
+
def initialize(search, term, prior_clause = nil, **term_options)
|
7
|
+
@search = search
|
8
|
+
@prior_clause = prior_clause
|
9
|
+
@not = false
|
10
|
+
|
11
|
+
initialize_term(term, **term_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
raise ArgumentError, "missing query terms" if term.blank?
|
16
|
+
|
17
|
+
[
|
18
|
+
prior_clause.presence,
|
19
|
+
queryify_term.dup.prepend(not_operator)
|
20
|
+
].compact.join(operand)
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
to_s.inspect
|
25
|
+
end
|
26
|
+
|
27
|
+
def not(term, **term_options)
|
28
|
+
@not = true
|
29
|
+
|
30
|
+
initialize_term(term, **term_options)
|
31
|
+
|
32
|
+
search
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :prior_clause, :term, :search
|
38
|
+
|
39
|
+
def operand
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def not_operator
|
44
|
+
return "" unless @not
|
45
|
+
|
46
|
+
"-"
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize_term(term, **term_options)
|
50
|
+
return if term.blank?
|
51
|
+
|
52
|
+
@term =
|
53
|
+
if term.is_a? RediSearch::Search
|
54
|
+
term
|
55
|
+
else
|
56
|
+
Term.new(term, term_options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def queryify_term
|
61
|
+
if term.is_a?(RediSearch::Search) &&
|
62
|
+
!term.term_clause.is_a?(RediSearch::Search::WhereClause)
|
63
|
+
"(#{term.term_clause})"
|
64
|
+
elsif term.is_a?(RediSearch::Search)
|
65
|
+
term.term_clause
|
66
|
+
else
|
67
|
+
term
|
68
|
+
end.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/term"
|
4
|
+
require "redi_search/search/and_clause"
|
5
|
+
require "redi_search/search/or_clause"
|
6
|
+
require "redi_search/search/where_clause"
|
7
|
+
|
8
|
+
module RediSearch
|
9
|
+
class Search
|
10
|
+
module Clauses
|
11
|
+
def highlight(**options)
|
12
|
+
clauses.push(*HighlightClause.new(**options).clause)
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def slop(slop)
|
18
|
+
clauses.push("SLOP", slop)
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def in_order
|
24
|
+
clauses.push("INORDER")
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_content
|
30
|
+
@no_content = true
|
31
|
+
clauses.push("NOCONTENT")
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def language(language)
|
37
|
+
clauses.push("LANGUAGE", language)
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def sort_by(field, order: :asc)
|
43
|
+
raise ArgumentError unless %i(asc desc).include?(order.to_sym)
|
44
|
+
|
45
|
+
clauses.push("SORTBY", field, order)
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def limit(num, offset = 0)
|
51
|
+
clauses.push("LIMIT", offset, num)
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def where(**condition)
|
57
|
+
@term_clause = WhereClause.new(self, condition, @term_clause)
|
58
|
+
|
59
|
+
if condition.blank?
|
60
|
+
@term_clause
|
61
|
+
else
|
62
|
+
self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def and(new_term = nil, **term_options)
|
67
|
+
@term_clause =
|
68
|
+
AndClause.new(self, new_term, @term_clause, **term_options)
|
69
|
+
|
70
|
+
if new_term.blank?
|
71
|
+
@term_clause
|
72
|
+
else
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def or(new_term = nil, **term_options)
|
78
|
+
@term_clause =
|
79
|
+
OrClause.new(self, new_term, @term_clause, **term_options)
|
80
|
+
|
81
|
+
if new_term.blank?
|
82
|
+
@term_clause
|
83
|
+
else
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
class HighlightClause
|
6
|
+
def initialize(**options)
|
7
|
+
@options = options.to_h
|
8
|
+
|
9
|
+
parse_options
|
10
|
+
end
|
11
|
+
|
12
|
+
def clause
|
13
|
+
[
|
14
|
+
"HIGHLIGHT",
|
15
|
+
(fields_clause(**options[:fields]) if options.key?(:fields)),
|
16
|
+
(tags_clause(**options[:tags]) if options.key? :tags),
|
17
|
+
].compact.flatten(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :options, :tags_args, :fields_args
|
23
|
+
|
24
|
+
def parse_options
|
25
|
+
return if options.except(:fields, :tags).empty?
|
26
|
+
|
27
|
+
arg_error "Unsupported argument: #{options}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def tags_clause(open:, close:)
|
31
|
+
["TAGS", open, close]
|
32
|
+
end
|
33
|
+
|
34
|
+
def fields_clause(num:, field:)
|
35
|
+
["FIELDS", num, field]
|
36
|
+
end
|
37
|
+
|
38
|
+
def arg_error(msg)
|
39
|
+
raise ArgumentError, "Highlight: #{msg}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/boolean_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
class OrClause < BooleanClause
|
8
|
+
def where(**condition)
|
9
|
+
@term = search.dup.where(condition)
|
10
|
+
|
11
|
+
search
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def operand
|
17
|
+
"|"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|