redi_search 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
module Clauses
|
6
|
+
class Boolean
|
7
|
+
def initialize(search, term, prior_clause = nil, **term_options)
|
8
|
+
@search = search
|
9
|
+
@prior_clause = prior_clause
|
10
|
+
@not = false
|
11
|
+
|
12
|
+
initialize_term(term, **term_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
raise ArgumentError, "missing query terms" if term.blank?
|
17
|
+
|
18
|
+
[
|
19
|
+
prior_clause.presence,
|
20
|
+
queryify_term.dup.prepend(not_operator)
|
21
|
+
].compact.join(operand)
|
22
|
+
end
|
23
|
+
|
24
|
+
delegate :inspect, to: :to_s
|
25
|
+
|
26
|
+
def not(term, **term_options)
|
27
|
+
@not = true
|
28
|
+
|
29
|
+
initialize_term(term, **term_options)
|
30
|
+
|
31
|
+
search
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :prior_clause, :term, :search
|
37
|
+
|
38
|
+
def operand
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
def not_operator
|
43
|
+
return "" unless @not
|
44
|
+
|
45
|
+
"-"
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize_term(term, **term_options)
|
49
|
+
return if term.blank?
|
50
|
+
|
51
|
+
@term =
|
52
|
+
if term.is_a? RediSearch::Search
|
53
|
+
term
|
54
|
+
else
|
55
|
+
Term.new(term, term_options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def queryify_term
|
60
|
+
if term.is_a?(RediSearch::Search) &&
|
61
|
+
!term.term_clause.is_a?(RediSearch::Search::Clauses::Where)
|
62
|
+
"(#{term.term_clause})"
|
63
|
+
elsif term.is_a?(RediSearch::Search)
|
64
|
+
term.term_clause
|
65
|
+
else
|
66
|
+
term
|
67
|
+
end.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
module Clauses
|
6
|
+
class Highlight
|
7
|
+
def initialize(fields: [], opening_tag: "<b>", closing_tag: "</b>")
|
8
|
+
@fields = fields
|
9
|
+
@opening_tag = opening_tag
|
10
|
+
@closing_tag = closing_tag
|
11
|
+
end
|
12
|
+
|
13
|
+
def clause
|
14
|
+
[
|
15
|
+
"HIGHLIGHT",
|
16
|
+
fields_clause,
|
17
|
+
tags_clause,
|
18
|
+
].compact.flatten(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :fields, :opening_tag, :closing_tag
|
24
|
+
|
25
|
+
def tags_clause
|
26
|
+
return if opening_tag.blank? && closing_tag.blank?
|
27
|
+
|
28
|
+
if opening_tag.present? && closing_tag.present?
|
29
|
+
["TAGS", opening_tag, closing_tag]
|
30
|
+
else
|
31
|
+
arg_error("Missing opening or closing tag")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def fields_clause
|
36
|
+
return if fields.empty?
|
37
|
+
|
38
|
+
["FIELDS", fields.size, fields]
|
39
|
+
end
|
40
|
+
|
41
|
+
def arg_error(msg)
|
42
|
+
raise ArgumentError, "Highlight: #{msg}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class InOrder < ApplicationClause
|
9
|
+
def clause
|
10
|
+
validate!
|
11
|
+
|
12
|
+
["INORDER"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Language < ApplicationClause
|
9
|
+
clause_term :language, presence: true
|
10
|
+
|
11
|
+
def initialize(language:)
|
12
|
+
@language = language
|
13
|
+
end
|
14
|
+
|
15
|
+
def clause
|
16
|
+
validate!
|
17
|
+
|
18
|
+
["LANGUAGE", language]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Limit < ApplicationClause
|
9
|
+
clause_term :total, presence: true,
|
10
|
+
numericality: { greater_than_or_equal_to: 0 }
|
11
|
+
clause_term :offset, presence: true,
|
12
|
+
numericality: { greater_than_or_equal_to: 0 }
|
13
|
+
|
14
|
+
def initialize(total:, offset: 0)
|
15
|
+
@total = total
|
16
|
+
@offset = offset
|
17
|
+
end
|
18
|
+
|
19
|
+
def clause
|
20
|
+
validate!
|
21
|
+
|
22
|
+
["LIMIT", [offset, total]]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class NoContent < ApplicationClause
|
9
|
+
def clause
|
10
|
+
validate!
|
11
|
+
|
12
|
+
["NOCONTENT"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class NoStopWords < ApplicationClause
|
9
|
+
def clause
|
10
|
+
validate!
|
11
|
+
|
12
|
+
["NOSTOPWORDS"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/boolean"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Or < Boolean
|
9
|
+
def where(**condition)
|
10
|
+
@term = search.dup.where(condition)
|
11
|
+
|
12
|
+
search
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def operand
|
18
|
+
"|"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Return < ApplicationClause
|
9
|
+
clause_term :fields, presence: true
|
10
|
+
|
11
|
+
def initialize(fields:)
|
12
|
+
@fields = fields
|
13
|
+
end
|
14
|
+
|
15
|
+
def clause
|
16
|
+
validate!
|
17
|
+
|
18
|
+
["RETURN", fields.size, fields]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Slop < ApplicationClause
|
9
|
+
clause_term :slop, numericality: { greater_than_or_equal_to: 0 }
|
10
|
+
|
11
|
+
def initialize(slop:)
|
12
|
+
@slop = slop
|
13
|
+
end
|
14
|
+
|
15
|
+
def clause
|
16
|
+
validate!
|
17
|
+
|
18
|
+
["SLOP", slop]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class SortBy < ApplicationClause
|
9
|
+
clause_term :field, presence: true
|
10
|
+
clause_term :order, presence: true, inclusion: { in: %i(asc desc) }
|
11
|
+
|
12
|
+
def initialize(field:, order: :asc)
|
13
|
+
@field = field
|
14
|
+
@order = order.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def clause
|
18
|
+
validate!
|
19
|
+
|
20
|
+
["SORTBY", field, order]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class Verbatim < ApplicationClause
|
9
|
+
def clause
|
10
|
+
validate!
|
11
|
+
|
12
|
+
["VERBATIM"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
module Clauses
|
6
|
+
class Where
|
7
|
+
def initialize(search, condition, prior_clause = nil)
|
8
|
+
@search = search
|
9
|
+
@prior_clause = prior_clause
|
10
|
+
@not = false
|
11
|
+
|
12
|
+
initialize_term(condition)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
[
|
17
|
+
prior_clause.presence,
|
18
|
+
"(#{not_operator}@#{field}:#{queryify_term})"
|
19
|
+
].compact.join(" ")
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate :inspect, to: :to_s
|
23
|
+
|
24
|
+
def not(condition)
|
25
|
+
@not = true
|
26
|
+
|
27
|
+
initialize_term(condition)
|
28
|
+
|
29
|
+
search
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :prior_clause, :term, :field, :search
|
35
|
+
|
36
|
+
def queryify_term
|
37
|
+
if term.is_a? RediSearch::Search
|
38
|
+
"(#{term.term_clause})"
|
39
|
+
else
|
40
|
+
term.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def not_operator
|
45
|
+
return "" unless @not
|
46
|
+
|
47
|
+
"-"
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize_term(condition)
|
51
|
+
return if condition.blank?
|
52
|
+
|
53
|
+
condition, *options = condition.to_a
|
54
|
+
|
55
|
+
@field = condition[0]
|
56
|
+
@term =
|
57
|
+
if condition[1].is_a? RediSearch::Search
|
58
|
+
condition[1]
|
59
|
+
else
|
60
|
+
Term.new(condition[1], **options.to_h)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses/application_clause"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Search
|
7
|
+
module Clauses
|
8
|
+
class WithScores < ApplicationClause
|
9
|
+
def clause
|
10
|
+
validate!
|
11
|
+
|
12
|
+
["WITHSCORES"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
class Result < Array
|
6
|
+
def initialize(index, used_clauses, count, documents)
|
7
|
+
@count = count
|
8
|
+
@used_clauses = used_clauses
|
9
|
+
|
10
|
+
super(documents.each_slice(response_slice).map do |slice|
|
11
|
+
document_id = slice[0]
|
12
|
+
fields = slice.last unless no_content?
|
13
|
+
score = slice[1].to_f if with_scores?
|
14
|
+
|
15
|
+
Document.new(index, document_id, Hash[*fields.to_a], score)
|
16
|
+
end)
|
17
|
+
end
|
18
|
+
|
19
|
+
def count
|
20
|
+
@count || super
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
@count || super
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def response_slice
|
30
|
+
slice_length = 2
|
31
|
+
slice_length -= 1 if no_content?
|
32
|
+
slice_length += 1 if with_scores?
|
33
|
+
|
34
|
+
slice_length
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_scores?
|
38
|
+
@used_clauses.include? "with_scores"
|
39
|
+
end
|
40
|
+
|
41
|
+
def no_content?
|
42
|
+
@used_clauses.include? "no_content"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|