redi_search 1.0.3 → 2.0.2
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/.github/workflows/lint.yml +20 -0
- data/.github/workflows/tests.yml +44 -0
- data/.rubocop.yml +67 -189
- data/Appraisals +4 -8
- data/Gemfile +3 -4
- data/README.md +79 -74
- data/Rakefile +15 -3
- data/bin/console +29 -0
- data/gemfiles/{rails_6.gemfile → activerecord_51.gemfile} +1 -5
- data/gemfiles/{rails_51.gemfile → activerecord_52.gemfile} +1 -5
- data/lib/redi_search.rb +8 -6
- data/lib/redi_search/add.rb +9 -5
- data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
- data/lib/redi_search/client.rb +8 -6
- data/lib/redi_search/client/response.rb +8 -8
- data/lib/redi_search/configuration.rb +1 -11
- data/lib/redi_search/create.rb +6 -4
- data/lib/redi_search/document.rb +5 -4
- data/lib/redi_search/document/display.rb +9 -9
- data/lib/redi_search/document/finder.rb +10 -2
- data/lib/redi_search/index.rb +9 -9
- data/lib/redi_search/lazily_load.rb +7 -13
- data/lib/redi_search/log_subscriber.rb +23 -52
- data/lib/redi_search/model.rb +43 -39
- data/lib/redi_search/schema.rb +3 -3
- data/lib/redi_search/schema/field.rb +2 -3
- data/lib/redi_search/schema/tag_field.rb +1 -1
- data/lib/redi_search/schema/text_field.rb +1 -1
- data/lib/redi_search/search.rb +15 -26
- data/lib/redi_search/search/clauses.rb +6 -7
- data/lib/redi_search/search/clauses/application_clause.rb +20 -5
- data/lib/redi_search/search/clauses/boolean.rb +8 -6
- data/lib/redi_search/search/clauses/highlight.rb +18 -2
- data/lib/redi_search/search/clauses/limit.rb +7 -5
- data/lib/redi_search/search/clauses/return.rb +1 -1
- data/lib/redi_search/search/clauses/slop.rb +1 -1
- data/lib/redi_search/search/clauses/sort_by.rb +1 -1
- data/lib/redi_search/search/clauses/where.rb +13 -3
- data/lib/redi_search/search/result.rb +27 -11
- data/lib/redi_search/search/term.rb +7 -6
- data/lib/redi_search/spellcheck.rb +3 -4
- data/lib/redi_search/spellcheck/result.rb +1 -1
- data/lib/redi_search/validatable.rb +49 -0
- data/lib/redi_search/validations/inclusion.rb +26 -0
- data/lib/redi_search/validations/numericality.rb +45 -0
- data/lib/redi_search/validations/presence.rb +29 -0
- data/lib/redi_search/version.rb +1 -1
- data/redi_search.gemspec +1 -3
- metadata +14 -45
- data/.travis.yml +0 -31
- data/bin/test +0 -7
- data/gemfiles/rails_52.gemfile +0 -17
@@ -4,26 +4,28 @@ module RediSearch
|
|
4
4
|
class Search
|
5
5
|
module Clauses
|
6
6
|
class Boolean
|
7
|
+
extend Forwardable
|
8
|
+
|
7
9
|
def initialize(search, term, prior_clause = nil, **term_options)
|
8
10
|
@search = search
|
9
11
|
@prior_clause = prior_clause
|
10
12
|
@not = false
|
11
13
|
|
12
|
-
initialize_term(term, **term_options) if term
|
14
|
+
initialize_term(term, **term_options) if term
|
13
15
|
end
|
14
16
|
|
15
17
|
def to_s
|
16
|
-
raise ArgumentError, "missing query terms"
|
18
|
+
raise ArgumentError, "missing query terms" unless term
|
17
19
|
|
18
|
-
[prior_clause
|
20
|
+
[prior_clause, queryify_term].compact.join(operand)
|
19
21
|
end
|
20
22
|
|
21
|
-
|
23
|
+
def_delegator :to_s, :inspect
|
22
24
|
|
23
25
|
def not(term, **term_options)
|
24
26
|
@not = true
|
25
27
|
|
26
|
-
initialize_term(term, **term_options) if term
|
28
|
+
initialize_term(term, **term_options) if term
|
27
29
|
|
28
30
|
search
|
29
31
|
end
|
@@ -33,7 +35,7 @@ module RediSearch
|
|
33
35
|
attr_reader :prior_clause, :term, :search
|
34
36
|
|
35
37
|
def operand
|
36
|
-
raise NotImplementedError
|
38
|
+
raise NotImplementedError, "#{__method__} needs to be defined"
|
37
39
|
end
|
38
40
|
|
39
41
|
def not_operator
|
@@ -23,9 +23,9 @@ module RediSearch
|
|
23
23
|
attr_reader :fields, :opening_tag, :closing_tag
|
24
24
|
|
25
25
|
def tags_clause
|
26
|
-
return if opening_tag
|
26
|
+
return if !opening_tag? && !closing_tag?
|
27
27
|
|
28
|
-
if opening_tag
|
28
|
+
if opening_tag? && closing_tag?
|
29
29
|
["TAGS", opening_tag, closing_tag]
|
30
30
|
else
|
31
31
|
arg_error("Missing opening or closing tag")
|
@@ -41,6 +41,22 @@ module RediSearch
|
|
41
41
|
def arg_error(msg)
|
42
42
|
raise ArgumentError, "Highlight: #{msg}"
|
43
43
|
end
|
44
|
+
|
45
|
+
def opening_tag?
|
46
|
+
if opening_tag.respond_to? :empty?
|
47
|
+
!opening_tag.empty?
|
48
|
+
else
|
49
|
+
opening_tag
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def closing_tag?
|
54
|
+
if closing_tag.respond_to? :empty?
|
55
|
+
!closing_tag.empty?
|
56
|
+
else
|
57
|
+
closing_tag
|
58
|
+
end
|
59
|
+
end
|
44
60
|
end
|
45
61
|
end
|
46
62
|
end
|
@@ -6,10 +6,12 @@ module RediSearch
|
|
6
6
|
class Search
|
7
7
|
module Clauses
|
8
8
|
class Limit < ApplicationClause
|
9
|
-
clause_term :total, presence: true,
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
clause_term :total, presence: true, numericality: {
|
10
|
+
within: 0..Float::INFINITY, only_integer: true
|
11
|
+
}
|
12
|
+
clause_term :offset, presence: true, numericality: {
|
13
|
+
within: 0..Float::INFINITY, only_integer: true
|
14
|
+
}
|
13
15
|
|
14
16
|
def initialize(total:, offset: 0)
|
15
17
|
@total = total
|
@@ -19,7 +21,7 @@ module RediSearch
|
|
19
21
|
def clause
|
20
22
|
validate!
|
21
23
|
|
22
|
-
["LIMIT",
|
24
|
+
["LIMIT", offset, total]
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -7,7 +7,7 @@ module RediSearch
|
|
7
7
|
module Clauses
|
8
8
|
class SortBy < ApplicationClause
|
9
9
|
clause_term :field, presence: true
|
10
|
-
clause_term :order, presence: true, inclusion: {
|
10
|
+
clause_term :order, presence: true, inclusion: { within: %i(asc desc) }
|
11
11
|
|
12
12
|
def initialize(field:, order: :asc)
|
13
13
|
@field = field
|
@@ -4,6 +4,8 @@ module RediSearch
|
|
4
4
|
class Search
|
5
5
|
module Clauses
|
6
6
|
class Where
|
7
|
+
extend Forwardable
|
8
|
+
|
7
9
|
def initialize(search, condition, prior_clause = nil)
|
8
10
|
@search = search
|
9
11
|
@prior_clause = prior_clause
|
@@ -14,12 +16,12 @@ module RediSearch
|
|
14
16
|
|
15
17
|
def to_s
|
16
18
|
[
|
17
|
-
prior_clause
|
19
|
+
prior_clause,
|
18
20
|
"(#{not_operator}@#{field}:#{queryify_term})"
|
19
21
|
].compact.join(" ")
|
20
22
|
end
|
21
23
|
|
22
|
-
|
24
|
+
def_delegator :to_s, :inspect
|
23
25
|
|
24
26
|
def not(condition)
|
25
27
|
@not = true
|
@@ -48,7 +50,7 @@ module RediSearch
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def initialize_term(condition)
|
51
|
-
return if condition
|
53
|
+
return if condition?(condition)
|
52
54
|
|
53
55
|
condition, *options = condition.to_a
|
54
56
|
|
@@ -63,6 +65,14 @@ module RediSearch
|
|
63
65
|
Term.new(condition[1], **options.to_h)
|
64
66
|
end
|
65
67
|
end
|
68
|
+
|
69
|
+
def condition?(condition)
|
70
|
+
if condition.respond_to?(:empty?)
|
71
|
+
condition.empty?
|
72
|
+
else
|
73
|
+
!condition
|
74
|
+
end
|
75
|
+
end
|
66
76
|
end
|
67
77
|
end
|
68
78
|
end
|
@@ -2,24 +2,40 @@
|
|
2
2
|
|
3
3
|
module RediSearch
|
4
4
|
class Search
|
5
|
-
class Result
|
6
|
-
|
7
|
-
|
8
|
-
@used_clauses = used_clauses
|
5
|
+
class Result
|
6
|
+
extend Forwardable
|
7
|
+
include Enumerable
|
9
8
|
|
10
|
-
|
9
|
+
def initialize(search, count, documents)
|
10
|
+
@count = count
|
11
|
+
@search = search
|
12
|
+
@results = parse_results(documents)
|
11
13
|
end
|
12
14
|
|
13
15
|
def count
|
14
|
-
@count ||
|
16
|
+
@count || results.count
|
15
17
|
end
|
16
18
|
|
17
19
|
def size
|
18
|
-
@count ||
|
20
|
+
@count || results.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def_delegators :results, :each, :empty?, :[], :last
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
results
|
27
|
+
end
|
28
|
+
|
29
|
+
#:nocov:
|
30
|
+
def pretty_print(printer)
|
31
|
+
printer.pp(results)
|
19
32
|
end
|
33
|
+
#:nocov:
|
20
34
|
|
21
35
|
private
|
22
36
|
|
37
|
+
attr_reader :results, :search
|
38
|
+
|
23
39
|
def response_slice
|
24
40
|
slice_length = 2
|
25
41
|
slice_length -= 1 if no_content?
|
@@ -29,20 +45,20 @@ module RediSearch
|
|
29
45
|
end
|
30
46
|
|
31
47
|
def with_scores?
|
32
|
-
|
48
|
+
search.used_clauses.include? Search::Clauses::WithScores
|
33
49
|
end
|
34
50
|
|
35
51
|
def no_content?
|
36
|
-
|
52
|
+
search.used_clauses.include? Search::Clauses::NoContent
|
37
53
|
end
|
38
54
|
|
39
|
-
def parse_results(
|
55
|
+
def parse_results(documents)
|
40
56
|
documents.each_slice(response_slice).map do |slice|
|
41
57
|
document_id = slice[0]
|
42
58
|
fields = slice.last unless no_content?
|
43
59
|
score = slice[1].to_f if with_scores?
|
44
60
|
|
45
|
-
Document.new(index, document_id, Hash[*fields.to_a], score)
|
61
|
+
Document.new(search.index, document_id, Hash[*fields.to_a], score)
|
46
62
|
end
|
47
63
|
end
|
48
64
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "redi_search/validatable"
|
4
|
+
|
3
5
|
module RediSearch
|
4
6
|
class Search
|
5
7
|
class Term
|
6
|
-
include
|
8
|
+
include Validatable
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
allow_nil: true
|
10
|
+
validates_numericality_of :fuzziness, within: 1..3, only_integer: true,
|
11
|
+
allow_nil: true
|
12
|
+
validates_inclusion_of :option, within: %i(fuzziness optional prefix),
|
13
|
+
allow_nil: true
|
13
14
|
|
14
15
|
def initialize(term, **options)
|
15
16
|
@term = term
|
@@ -2,15 +2,14 @@
|
|
2
2
|
|
3
3
|
require "redi_search/lazily_load"
|
4
4
|
require "redi_search/spellcheck/result"
|
5
|
+
require "redi_search/validatable"
|
5
6
|
|
6
7
|
module RediSearch
|
7
8
|
class Spellcheck
|
8
9
|
include LazilyLoad
|
9
|
-
include
|
10
|
+
include Validatable
|
10
11
|
|
11
|
-
|
12
|
-
greater_than: 0, less_than: 5
|
13
|
-
}
|
12
|
+
validates_numericality_of :distance, within: 1..4, only_integer: true
|
14
13
|
|
15
14
|
def initialize(index, terms, distance: 1)
|
16
15
|
@index = index
|
@@ -14,7 +14,6 @@ module RediSearch
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
#:nocov:
|
18
17
|
def inspect
|
19
18
|
inspection = %w(term suggestions).map do |field_name|
|
20
19
|
"#{field_name}: #{public_send(field_name)}"
|
@@ -23,6 +22,7 @@ module RediSearch
|
|
23
22
|
"#<#{self.class} #{inspection}>"
|
24
23
|
end
|
25
24
|
|
25
|
+
#:nocov:
|
26
26
|
def pretty_print(printer) # rubocop:disable Metrics/MethodLength
|
27
27
|
printer.object_address_group(self) do
|
28
28
|
printer.seplist(
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/validations/inclusion"
|
4
|
+
require "redi_search/validations/presence"
|
5
|
+
require "redi_search/validations/numericality"
|
6
|
+
|
7
|
+
module RediSearch
|
8
|
+
class ValidationError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
module Validatable
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
attr_accessor :validations
|
18
|
+
|
19
|
+
def validates_inclusion_of(field, within:, **options)
|
20
|
+
self.validations = [
|
21
|
+
*validations.to_a,
|
22
|
+
Validations::Inclusion.new(field: field, within: within, **options)
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
def validates_presence_of(field)
|
27
|
+
self.validations = [
|
28
|
+
*validations.to_a,
|
29
|
+
Validations::Presence.new(field: field)
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def validates_numericality_of(field, within:, **options)
|
34
|
+
self.validations = [
|
35
|
+
*validations.to_a,
|
36
|
+
Validations::Numericality.new(
|
37
|
+
field: field, within: within, **options
|
38
|
+
)
|
39
|
+
]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate!
|
44
|
+
self.class.validations.to_a.each do |validator|
|
45
|
+
validator.validate!(self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
module Validations
|
5
|
+
class Inclusion
|
6
|
+
def initialize(field:, within:, allow_nil: false)
|
7
|
+
@field = field
|
8
|
+
@within = within
|
9
|
+
@allow_nil = allow_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate!(object)
|
13
|
+
value = object.send(field)
|
14
|
+
|
15
|
+
return true if within.include?(value) || (allow_nil? && value.nil?)
|
16
|
+
|
17
|
+
raise ValidationError, "#{value.inspect} not included in #{within}"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :field, :within, :allow_nil
|
23
|
+
alias allow_nil? allow_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/validations/inclusion"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
module Validations
|
7
|
+
class Numericality
|
8
|
+
def initialize(field:, within:, only_integer: false, allow_nil: false)
|
9
|
+
@field = field
|
10
|
+
@within = within
|
11
|
+
@only_integer = only_integer
|
12
|
+
@allow_nil = allow_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate!(object)
|
16
|
+
value = object.send(field)
|
17
|
+
|
18
|
+
return true if value.nil? && allow_nil?
|
19
|
+
|
20
|
+
validate_numberness!(value)
|
21
|
+
validate_inclusion!(object)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :field, :within, :only_integer, :allow_nil
|
27
|
+
alias only_integer? only_integer
|
28
|
+
alias allow_nil? allow_nil
|
29
|
+
|
30
|
+
def validate_numberness!(value)
|
31
|
+
raise(ValidationError, "#{field} must be a number") unless
|
32
|
+
value.is_a?(Numeric)
|
33
|
+
|
34
|
+
raise(ValidationError, "#{field} must be an Integer") if
|
35
|
+
only_integer? && !value.is_a?(Integer)
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_inclusion!(object)
|
41
|
+
Inclusion.new(field: field, within: within).validate!(object)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
module Validations
|
5
|
+
class Presence
|
6
|
+
def initialize(field:)
|
7
|
+
@field = field
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!(object)
|
11
|
+
return true if value_present?(object.send(field))
|
12
|
+
|
13
|
+
raise RediSearch::ValidationError, "#{field} can't be blank"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :field
|
19
|
+
|
20
|
+
def value_present?(value)
|
21
|
+
if value.respond_to?(:empty?)
|
22
|
+
!value.empty?
|
23
|
+
else
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|