redi_search 0.1.0 → 1.0.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/README.md +516 -112
- data/lib/redi_search.rb +5 -2
- data/lib/redi_search/add.rb +70 -0
- data/lib/redi_search/alter.rb +30 -0
- data/lib/redi_search/create.rb +53 -0
- data/lib/redi_search/document.rb +71 -16
- data/lib/redi_search/index.rb +31 -26
- data/lib/redi_search/lazily_load.rb +65 -0
- data/lib/redi_search/log_subscriber.rb +4 -0
- data/lib/redi_search/model.rb +41 -18
- data/lib/redi_search/schema.rb +17 -8
- data/lib/redi_search/schema/text_field.rb +0 -2
- data/lib/redi_search/search.rb +22 -44
- data/lib/redi_search/search/clauses.rb +60 -31
- data/lib/redi_search/search/clauses/and.rb +17 -0
- data/lib/redi_search/search/clauses/application_clause.rb +18 -0
- data/lib/redi_search/search/clauses/boolean.rb +72 -0
- data/lib/redi_search/search/clauses/highlight.rb +47 -0
- data/lib/redi_search/search/clauses/in_order.rb +17 -0
- data/lib/redi_search/search/clauses/language.rb +23 -0
- data/lib/redi_search/search/clauses/limit.rb +27 -0
- data/lib/redi_search/search/clauses/no_content.rb +17 -0
- data/lib/redi_search/search/clauses/no_stop_words.rb +17 -0
- data/lib/redi_search/search/clauses/or.rb +23 -0
- data/lib/redi_search/search/clauses/return.rb +23 -0
- data/lib/redi_search/search/clauses/slop.rb +23 -0
- data/lib/redi_search/search/clauses/sort_by.rb +25 -0
- data/lib/redi_search/search/clauses/verbatim.rb +17 -0
- data/lib/redi_search/search/clauses/where.rb +66 -0
- data/lib/redi_search/search/clauses/with_scores.rb +17 -0
- data/lib/redi_search/search/result.rb +46 -0
- data/lib/redi_search/search/term.rb +4 -4
- data/lib/redi_search/spellcheck.rb +30 -29
- data/lib/redi_search/spellcheck/result.rb +44 -0
- data/lib/redi_search/version.rb +1 -1
- metadata +101 -31
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -1757
- data/.travis.yml +0 -23
- data/Gemfile +0 -17
- data/Rakefile +0 -12
- data/bin/console +0 -8
- data/bin/publish +0 -58
- data/bin/setup +0 -8
- data/bin/test +0 -7
- data/lib/redi_search/document/converter.rb +0 -26
- data/lib/redi_search/error.rb +0 -6
- data/lib/redi_search/result/collection.rb +0 -22
- data/lib/redi_search/search/and_clause.rb +0 -15
- data/lib/redi_search/search/boolean_clause.rb +0 -72
- data/lib/redi_search/search/highlight_clause.rb +0 -43
- data/lib/redi_search/search/or_clause.rb +0 -21
- data/lib/redi_search/search/where_clause.rb +0 -66
- data/redi_search.gemspec +0 -48
data/lib/redi_search/model.rb
CHANGED
@@ -1,57 +1,80 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "redi_search/index"
|
4
|
-
require "redi_search/document/converter"
|
5
|
-
|
6
4
|
require "active_support/concern"
|
7
5
|
|
8
6
|
module RediSearch
|
9
7
|
module Model
|
10
8
|
extend ActiveSupport::Concern
|
11
9
|
|
10
|
+
# rubocop:disable Metrics/BlockLength
|
12
11
|
class_methods do
|
13
|
-
attr_reader :redi_search_index
|
12
|
+
attr_reader :redi_search_index, :redi_search_serializer
|
14
13
|
|
15
|
-
|
14
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
15
|
+
def redi_search(schema:, **options)
|
16
16
|
@redi_search_index = Index.new(
|
17
|
-
options[:
|
18
|
-
|
17
|
+
[options[:index_prefix],
|
18
|
+
model_name.plural, RediSearch.env].compact.join("_"),
|
19
|
+
schema,
|
19
20
|
self
|
20
21
|
)
|
22
|
+
@redi_search_serializer = options[:serializer]
|
23
|
+
register_redi_search_commit_hooks
|
21
24
|
|
22
|
-
|
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
|
25
|
+
scope :search_import, -> { all }
|
28
26
|
|
29
27
|
class << self
|
30
28
|
def search(term = nil, **term_options)
|
31
29
|
redi_search_index.search(term, **term_options)
|
32
30
|
end
|
33
31
|
|
34
|
-
def
|
35
|
-
redi_search_index.
|
32
|
+
def spellcheck(term, distance: 1)
|
33
|
+
redi_search_index.spellcheck(term, distance: distance)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reindex(only: [], **options)
|
37
|
+
search_import.find_in_batches.all? do |group|
|
38
|
+
redi_search_index.reindex(
|
39
|
+
group.map { |record| record.redi_search_document(only: only) },
|
40
|
+
**options.deep_merge(replace: { partial: true })
|
41
|
+
)
|
42
|
+
end
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
46
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def register_redi_search_commit_hooks
|
51
|
+
after_commit(:redi_search_add_document, on: %i(create update)) if
|
52
|
+
respond_to?(:after_commit)
|
53
|
+
after_destroy_commit(:redi_search_delete_document) if
|
54
|
+
respond_to?(:after_destroy_commit)
|
55
|
+
end
|
39
56
|
end
|
57
|
+
# rubocop:enable Metrics/BlockLength
|
40
58
|
|
41
|
-
def redi_search_document
|
42
|
-
Document
|
59
|
+
def redi_search_document(only: [])
|
60
|
+
Document.for_object(
|
61
|
+
self.class.redi_search_index, self,
|
62
|
+
only: only, serializer: self.class.redi_search_serializer
|
63
|
+
)
|
43
64
|
end
|
44
65
|
|
45
66
|
def redi_search_delete_document
|
46
67
|
return unless self.class.redi_search_index.exist?
|
47
68
|
|
48
|
-
self.class.redi_search_index.del(
|
69
|
+
self.class.redi_search_index.del(
|
70
|
+
redi_search_document, delete_document: true
|
71
|
+
)
|
49
72
|
end
|
50
73
|
|
51
74
|
def redi_search_add_document
|
52
75
|
return unless self.class.redi_search_index.exist?
|
53
76
|
|
54
|
-
self.class.redi_search_index.add(
|
77
|
+
self.class.redi_search_index.add(redi_search_document, replace: true)
|
55
78
|
end
|
56
79
|
end
|
57
80
|
end
|
data/lib/redi_search/schema.rb
CHANGED
@@ -7,19 +7,23 @@ require "redi_search/schema/text_field"
|
|
7
7
|
|
8
8
|
module RediSearch
|
9
9
|
class Schema
|
10
|
+
def self.make_field(field_name, options)
|
11
|
+
options = [options] if options.is_a? Symbol
|
12
|
+
schema, options = options.to_a.flatten
|
13
|
+
|
14
|
+
"RediSearch::Schema::#{schema.to_s.capitalize}Field".
|
15
|
+
constantize.
|
16
|
+
new(field_name, **options.to_h).
|
17
|
+
to_a
|
18
|
+
end
|
19
|
+
|
10
20
|
def initialize(raw)
|
11
21
|
@raw = raw
|
12
22
|
end
|
13
23
|
|
14
24
|
def to_a
|
15
25
|
raw.map do |field_name, options|
|
16
|
-
|
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
|
26
|
+
self.class.make_field(field_name, options)
|
23
27
|
end.flatten
|
24
28
|
end
|
25
29
|
|
@@ -27,8 +31,13 @@ module RediSearch
|
|
27
31
|
raw.keys
|
28
32
|
end
|
29
33
|
|
34
|
+
def alter(field_name, options)
|
35
|
+
raw[field_name] = options
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
30
39
|
private
|
31
40
|
|
32
|
-
|
41
|
+
attr_accessor :raw
|
33
42
|
end
|
34
43
|
end
|
data/lib/redi_search/search.rb
CHANGED
@@ -1,51 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "redi_search/lazily_load"
|
4
|
+
|
3
5
|
require "redi_search/search/clauses"
|
4
6
|
require "redi_search/search/term"
|
5
|
-
require "redi_search/search/
|
6
|
-
require "redi_search/result/collection"
|
7
|
+
require "redi_search/search/result"
|
7
8
|
|
8
9
|
module RediSearch
|
9
10
|
class Search
|
10
11
|
include Enumerable
|
12
|
+
include LazilyLoad
|
11
13
|
include Clauses
|
12
14
|
|
13
|
-
def initialize(index, term = nil,
|
15
|
+
def initialize(index, term = nil, **term_options)
|
14
16
|
@index = index
|
15
|
-
@model = model
|
16
|
-
@loaded = false
|
17
|
-
@no_content = false
|
18
17
|
@clauses = []
|
18
|
+
@used_clauses = Set.new
|
19
19
|
|
20
20
|
@term_clause = term.presence &&
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
#:nocov:
|
25
|
-
def pretty_print(printer)
|
26
|
-
execute unless loaded?
|
27
|
-
|
28
|
-
printer.pp(records)
|
29
|
-
rescue Redis::CommandError => e
|
30
|
-
printer.pp(e.message)
|
31
|
-
end
|
32
|
-
#:nocov:
|
33
|
-
|
34
|
-
def loaded?
|
35
|
-
@loaded
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_a
|
39
|
-
execute unless loaded?
|
40
|
-
|
41
|
-
@records
|
21
|
+
And.new(self, term, nil, **term_options)
|
42
22
|
end
|
43
23
|
|
44
24
|
def results
|
45
|
-
model.
|
25
|
+
if index.model.present?
|
26
|
+
index.model.where(id: to_a.map(&:document_id_without_index))
|
27
|
+
else
|
28
|
+
to_a
|
29
|
+
end
|
46
30
|
end
|
47
31
|
|
48
|
-
|
32
|
+
def explain
|
33
|
+
RediSearch.client.call!(
|
34
|
+
"EXPLAINCLI", index.name, term_clause
|
35
|
+
).join(" ").strip
|
36
|
+
end
|
49
37
|
|
50
38
|
def to_redis
|
51
39
|
command.map do |arg|
|
@@ -65,25 +53,15 @@ module RediSearch
|
|
65
53
|
|
66
54
|
private
|
67
55
|
|
68
|
-
attr_reader :
|
69
|
-
attr_accessor :index, :
|
56
|
+
attr_reader :documents, :used_clauses
|
57
|
+
attr_accessor :index, :clauses
|
70
58
|
|
71
59
|
def command
|
72
|
-
["SEARCH", index.name, term_clause, *clauses]
|
60
|
+
["SEARCH", index.name, term_clause, *clauses.uniq]
|
73
61
|
end
|
74
62
|
|
75
|
-
def
|
76
|
-
@
|
77
|
-
|
78
|
-
RediSearch.client.call!(*command).then do |results|
|
79
|
-
@records = Result::Collection.new(
|
80
|
-
index, results[0], results[1..-1].then do |docs|
|
81
|
-
next docs unless @no_content
|
82
|
-
|
83
|
-
docs.zip([[]] * results[0]).flatten(1)
|
84
|
-
end
|
85
|
-
)
|
86
|
-
end
|
63
|
+
def parse_response(response)
|
64
|
+
@documents = Result.new(index, used_clauses, response[0], response[1..-1])
|
87
65
|
end
|
88
66
|
end
|
89
67
|
end
|
@@ -1,60 +1,82 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "redi_search/search/term"
|
4
|
-
require "redi_search/search/
|
5
|
-
require "redi_search/search/
|
6
|
-
require "redi_search/search/
|
4
|
+
require "redi_search/search/clauses/slop"
|
5
|
+
require "redi_search/search/clauses/in_order"
|
6
|
+
require "redi_search/search/clauses/language"
|
7
|
+
require "redi_search/search/clauses/sort_by"
|
8
|
+
require "redi_search/search/clauses/limit"
|
9
|
+
require "redi_search/search/clauses/no_content"
|
10
|
+
require "redi_search/search/clauses/verbatim"
|
11
|
+
require "redi_search/search/clauses/no_stop_words"
|
12
|
+
require "redi_search/search/clauses/return"
|
13
|
+
require "redi_search/search/clauses/with_scores"
|
14
|
+
require "redi_search/search/clauses/highlight"
|
15
|
+
require "redi_search/search/clauses/and"
|
16
|
+
require "redi_search/search/clauses/or"
|
17
|
+
require "redi_search/search/clauses/where"
|
7
18
|
|
8
19
|
module RediSearch
|
9
20
|
class Search
|
10
21
|
module Clauses
|
11
|
-
def highlight(
|
12
|
-
|
13
|
-
|
14
|
-
|
22
|
+
def highlight(fields: [], opening_tag: "<b>", closing_tag: "</b>")
|
23
|
+
add_to_clause(Highlight.new(
|
24
|
+
fields: fields, opening_tag: opening_tag, closing_tag: closing_tag
|
25
|
+
))
|
15
26
|
end
|
16
27
|
|
17
28
|
def slop(slop)
|
18
|
-
|
19
|
-
|
20
|
-
self
|
29
|
+
add_to_clause(Slop.new(slop: slop))
|
21
30
|
end
|
22
31
|
|
23
32
|
def in_order
|
24
|
-
|
33
|
+
add_to_clause(InOrder.new)
|
34
|
+
end
|
25
35
|
|
26
|
-
|
36
|
+
def verbatim
|
37
|
+
add_to_clause(Verbatim.new)
|
38
|
+
end
|
39
|
+
|
40
|
+
def no_stop_words
|
41
|
+
add_to_clause(NoStopWords.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_scores
|
45
|
+
add_to_clause(WithScores.new)
|
27
46
|
end
|
28
47
|
|
29
48
|
def no_content
|
30
|
-
|
31
|
-
|
49
|
+
add_to_clause(NoContent.new)
|
50
|
+
end
|
32
51
|
|
33
|
-
|
52
|
+
def return(*fields)
|
53
|
+
add_to_clause(Return.new(fields: fields))
|
34
54
|
end
|
35
55
|
|
36
56
|
def language(language)
|
37
|
-
|
38
|
-
|
39
|
-
self
|
57
|
+
add_to_clause(Language.new(language: language))
|
40
58
|
end
|
41
59
|
|
42
60
|
def sort_by(field, order: :asc)
|
43
|
-
|
44
|
-
|
45
|
-
clauses.push("SORTBY", field, order)
|
46
|
-
|
47
|
-
self
|
61
|
+
add_to_clause(SortBy.new(field: field, order: order))
|
48
62
|
end
|
49
63
|
|
50
|
-
def limit(
|
51
|
-
|
64
|
+
def limit(total, offset = 0)
|
65
|
+
add_to_clause(Limit.new(total: total, offset: offset))
|
66
|
+
end
|
52
67
|
|
53
|
-
|
68
|
+
def count
|
69
|
+
if @loaded
|
70
|
+
to_a.size
|
71
|
+
else
|
72
|
+
RediSearch.client.call!(
|
73
|
+
"SEARCH", index.name, term_clause, *Limit.new(total: 0).clause
|
74
|
+
).first
|
75
|
+
end
|
54
76
|
end
|
55
77
|
|
56
78
|
def where(**condition)
|
57
|
-
@term_clause =
|
79
|
+
@term_clause = Where.new(self, condition, @term_clause)
|
58
80
|
|
59
81
|
if condition.blank?
|
60
82
|
@term_clause
|
@@ -64,8 +86,7 @@ module RediSearch
|
|
64
86
|
end
|
65
87
|
|
66
88
|
def and(new_term = nil, **term_options)
|
67
|
-
@term_clause =
|
68
|
-
AndClause.new(self, new_term, @term_clause, **term_options)
|
89
|
+
@term_clause = And.new(self, new_term, @term_clause, **term_options)
|
69
90
|
|
70
91
|
if new_term.blank?
|
71
92
|
@term_clause
|
@@ -75,8 +96,7 @@ module RediSearch
|
|
75
96
|
end
|
76
97
|
|
77
98
|
def or(new_term = nil, **term_options)
|
78
|
-
@term_clause =
|
79
|
-
OrClause.new(self, new_term, @term_clause, **term_options)
|
99
|
+
@term_clause = Or.new(self, new_term, @term_clause, **term_options)
|
80
100
|
|
81
101
|
if new_term.blank?
|
82
102
|
@term_clause
|
@@ -84,6 +104,15 @@ module RediSearch
|
|
84
104
|
self
|
85
105
|
end
|
86
106
|
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def add_to_clause(clause)
|
111
|
+
used_clauses.add(clause.class.name.demodulize.underscore)
|
112
|
+
clauses.push(*clause.clause)
|
113
|
+
|
114
|
+
self
|
115
|
+
end
|
87
116
|
end
|
88
117
|
end
|
89
118
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
module Clauses
|
6
|
+
class ApplicationClause
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
def self.clause_term(term, *validations)
|
10
|
+
attr_reader term
|
11
|
+
validations.each do |validation|
|
12
|
+
validates term, validation
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|