redi_search 1.0.4 → 3.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/.github/workflows/lint.yml +18 -0
- data/.github/workflows/tests.yml +45 -0
- data/.rubocop.yml +136 -193
- data/Appraisals +6 -6
- data/Gemfile +4 -4
- data/README.md +79 -92
- data/Rakefile +15 -3
- data/bin/console +29 -0
- data/gemfiles/{rails_51.gemfile → activerecord_51.gemfile} +3 -4
- data/gemfiles/{rails_52.gemfile → activerecord_52.gemfile} +3 -4
- data/gemfiles/{rails_6.gemfile → activerecord_61.gemfile} +3 -4
- data/lib/redi_search.rb +8 -7
- data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
- data/lib/redi_search/client.rb +23 -11
- data/lib/redi_search/client/response.rb +5 -1
- data/lib/redi_search/configuration.rb +1 -11
- data/lib/redi_search/create.rb +7 -4
- data/lib/redi_search/document.rb +5 -13
- data/lib/redi_search/document/display.rb +9 -9
- data/lib/redi_search/document/finder.rb +12 -42
- data/lib/redi_search/hset.rb +28 -0
- data/lib/redi_search/index.rb +24 -22
- data/lib/redi_search/lazily_load.rb +6 -11
- data/lib/redi_search/log_subscriber.rb +25 -53
- data/lib/redi_search/model.rb +37 -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 -25
- 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 +9 -9
- 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/or.rb +1 -1
- 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 +10 -2
- data/lib/redi_search/search/result.rb +10 -10
- 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 +20 -50
- data/.travis.yml +0 -31
- data/bin/test +0 -7
- data/lib/redi_search/add.rb +0 -63
@@ -2,12 +2,11 @@
|
|
2
2
|
|
3
3
|
module RediSearch
|
4
4
|
module LazilyLoad
|
5
|
-
extend ActiveSupport::Concern
|
6
5
|
extend Forwardable
|
7
6
|
|
8
7
|
include Enumerable
|
9
8
|
|
10
|
-
def_delegators :to_a, :size, :each
|
9
|
+
def_delegators :to_a, :size, :each, :last, :[], :count, :empty?
|
11
10
|
|
12
11
|
def loaded?
|
13
12
|
@loaded = false unless defined? @loaded
|
@@ -23,7 +22,6 @@ module RediSearch
|
|
23
22
|
|
24
23
|
alias load to_a
|
25
24
|
|
26
|
-
#:nocov:
|
27
25
|
def inspect
|
28
26
|
execute_and_rescue_inspection do
|
29
27
|
return super unless valid?
|
@@ -32,6 +30,7 @@ module RediSearch
|
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
33
|
+
#:nocov:
|
35
34
|
def pretty_print(printer)
|
36
35
|
execute_and_rescue_inspection do
|
37
36
|
return super(inspect) unless valid?
|
@@ -41,12 +40,10 @@ module RediSearch
|
|
41
40
|
end
|
42
41
|
#:nocov:
|
43
42
|
|
44
|
-
def count
|
45
|
-
to_a.size
|
46
|
-
end
|
47
|
-
|
48
43
|
private
|
49
44
|
|
45
|
+
attr_reader :documents
|
46
|
+
|
50
47
|
def command
|
51
48
|
raise NotImplementedError, "included class did not define #{__method__}"
|
52
49
|
end
|
@@ -56,12 +53,12 @@ module RediSearch
|
|
56
53
|
|
57
54
|
@loaded = true
|
58
55
|
|
59
|
-
call
|
56
|
+
call!.yield_self do |response|
|
60
57
|
parse_response(response)
|
61
58
|
end
|
62
59
|
end
|
63
60
|
|
64
|
-
def call!
|
61
|
+
def call!
|
65
62
|
RediSearch.client.call!(*command)
|
66
63
|
end
|
67
64
|
|
@@ -73,7 +70,6 @@ module RediSearch
|
|
73
70
|
true
|
74
71
|
end
|
75
72
|
|
76
|
-
#:nocov:
|
77
73
|
def execute_and_rescue_inspection
|
78
74
|
execute unless loaded?
|
79
75
|
|
@@ -81,6 +77,5 @@ module RediSearch
|
|
81
77
|
rescue Redis::CommandError => e
|
82
78
|
e.message
|
83
79
|
end
|
84
|
-
#:nocov:
|
85
80
|
end
|
86
81
|
end
|
@@ -1,96 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/log_subscriber"
|
4
|
+
|
3
5
|
module RediSearch
|
4
6
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
7
|
def self.runtime=(value)
|
6
|
-
Thread.current[:
|
8
|
+
Thread.current[:redi_search_runtime] = value
|
7
9
|
end
|
8
10
|
|
9
11
|
def self.runtime
|
10
|
-
Thread.current[:
|
12
|
+
Thread.current[:redi_search_runtime] ||= 0
|
11
13
|
end
|
12
14
|
|
13
15
|
#:nocov:
|
14
16
|
def self.reset_runtime
|
15
|
-
rt = runtime
|
16
|
-
self.runtime = 0
|
17
|
+
rt, self.runtime = runtime, 0
|
17
18
|
rt
|
18
19
|
end
|
19
20
|
#:nocov:
|
20
21
|
|
21
|
-
def
|
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
|
-
def explaincli(event)
|
62
|
-
log_command(event, BLUE)
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def log_command(event, debug_color)
|
22
|
+
def action(event)
|
68
23
|
self.class.runtime += event.duration
|
69
24
|
return unless logger.debug?
|
70
25
|
|
71
26
|
command = command_string(event)
|
27
|
+
debug_color = action_color(event.payload[:action])
|
72
28
|
|
73
29
|
debug " #{log_name(event)} #{color(command, debug_color, true)}"
|
74
30
|
end
|
75
31
|
|
32
|
+
private
|
33
|
+
|
76
34
|
def log_name(event)
|
77
35
|
color("#{event.payload[:name]} (#{event.duration.round(1)}ms)", RED, true)
|
78
36
|
end
|
79
37
|
|
38
|
+
# rubocop:disable Metrics/MethodLength
|
39
|
+
def action_color(action)
|
40
|
+
case action.to_sym
|
41
|
+
when :search, :spellcheck then YELLOW
|
42
|
+
when :create, :hset then GREEN
|
43
|
+
when :dropindex, :del then RED
|
44
|
+
when :hgetall, :info then CYAN
|
45
|
+
when :pipeline then MAGENTA
|
46
|
+
when :explaincli then BLUE
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# rubocop:enable Metrics/MethodLength
|
50
|
+
|
80
51
|
def command_string(event)
|
81
52
|
event.payload[:query].flatten.map.with_index do |arg, i|
|
82
53
|
arg = "FT.#{arg}" if prepend_ft?(arg, i)
|
83
54
|
arg = arg.inspect if inspect_arg?(event.payload, arg)
|
55
|
+
arg = " #{arg}" if event.payload[:inside_pipeline]
|
84
56
|
arg
|
85
57
|
end.join(" ")
|
86
58
|
end
|
87
59
|
|
88
60
|
def multiword?(string)
|
89
|
-
!string.to_s.
|
61
|
+
!string.to_s.start_with?(/\(-?@/) && string.to_s.split(/\s|\|/).size > 1
|
90
62
|
end
|
91
63
|
|
92
64
|
def prepend_ft?(arg, index)
|
93
|
-
index.zero? && !multiword?(arg)
|
65
|
+
index.zero? && !multiword?(arg) && %w(HSET HGETALL DEL).exclude?(arg.to_s)
|
94
66
|
end
|
95
67
|
|
96
68
|
def inspect_arg?(payload, arg)
|
data/lib/redi_search/model.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "redi_search/index"
|
4
|
-
require "active_support/concern"
|
5
4
|
|
6
5
|
module RediSearch
|
7
6
|
module Model
|
8
|
-
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
class_methods do
|
11
|
+
module ClassMethods
|
12
12
|
attr_reader :redi_search_index, :redi_search_serializer
|
13
13
|
|
14
|
-
# rubocop:disable Metrics/MethodLength
|
14
|
+
# rubocop:disable Metrics/MethodLength
|
15
15
|
def redi_search(schema:, **options)
|
16
16
|
@redi_search_index = Index.new(
|
17
17
|
[options[:index_prefix],
|
@@ -24,26 +24,10 @@ module RediSearch
|
|
24
24
|
|
25
25
|
scope :search_import, -> { all }
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
redi_search_index.search(term, **term_options)
|
30
|
-
end
|
31
|
-
|
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
|
43
|
-
end
|
44
|
-
end
|
27
|
+
include InstanceMethods
|
28
|
+
extend ModelClassMethods
|
45
29
|
end
|
46
|
-
# rubocop:enable Metrics/MethodLength
|
30
|
+
# rubocop:enable Metrics/MethodLength
|
47
31
|
|
48
32
|
private
|
49
33
|
|
@@ -54,27 +38,41 @@ module RediSearch
|
|
54
38
|
respond_to?(:after_destroy_commit)
|
55
39
|
end
|
56
40
|
end
|
57
|
-
# rubocop:enable Metrics/BlockLength
|
58
41
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
)
|
64
|
-
end
|
42
|
+
module ModelClassMethods
|
43
|
+
def search(term = nil, **term_options)
|
44
|
+
redi_search_index.search(term, **term_options)
|
45
|
+
end
|
65
46
|
|
66
|
-
|
67
|
-
|
47
|
+
def spellcheck(term, distance: 1)
|
48
|
+
redi_search_index.spellcheck(term, distance: distance)
|
49
|
+
end
|
68
50
|
|
69
|
-
|
70
|
-
|
71
|
-
|
51
|
+
def reindex(recreate: false, only: [])
|
52
|
+
search_import.find_in_batches.all? do |group|
|
53
|
+
redi_search_index.reindex(
|
54
|
+
group.map { |record| record.redi_search_document(only: only) },
|
55
|
+
recreate: recreate
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
72
59
|
end
|
73
60
|
|
74
|
-
|
75
|
-
|
61
|
+
module InstanceMethods
|
62
|
+
def redi_search_document(only: [])
|
63
|
+
Document.for_object(
|
64
|
+
self.class.redi_search_index, self,
|
65
|
+
only: only, serializer: self.class.redi_search_serializer
|
66
|
+
)
|
67
|
+
end
|
76
68
|
|
77
|
-
|
69
|
+
def redi_search_delete_document
|
70
|
+
self.class.redi_search_index.del(redi_search_document)
|
71
|
+
end
|
72
|
+
|
73
|
+
def redi_search_add_document
|
74
|
+
self.class.redi_search_index.add(redi_search_document)
|
75
|
+
end
|
78
76
|
end
|
79
77
|
end
|
80
78
|
end
|
data/lib/redi_search/schema.rb
CHANGED
@@ -11,8 +11,8 @@ module RediSearch
|
|
11
11
|
options = [options] if options.is_a? Symbol
|
12
12
|
schema, options = options.to_a.flatten
|
13
13
|
|
14
|
-
"RediSearch::Schema::#{schema.to_s.capitalize}Field".
|
15
|
-
|
14
|
+
Object.const_get("RediSearch::Schema::#{schema.to_s.capitalize}Field").
|
15
|
+
new(field_name, **options.to_h).to_a
|
16
16
|
end
|
17
17
|
|
18
18
|
def initialize(raw)
|
@@ -29,7 +29,7 @@ module RediSearch
|
|
29
29
|
raw.keys
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def add_field(field_name, options)
|
33
33
|
raw[field_name] = options
|
34
34
|
self
|
35
35
|
end
|
@@ -11,9 +11,8 @@ module RediSearch
|
|
11
11
|
|
12
12
|
def boolean_options_string
|
13
13
|
boolean_options.map do |option|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
14
|
+
option.to_s.upcase.split("_").join unless
|
15
|
+
FALSES.include?(send(option))
|
17
16
|
end.compact
|
18
17
|
end
|
19
18
|
end
|
data/lib/redi_search/search.rb
CHANGED
@@ -8,21 +8,28 @@ require "redi_search/search/result"
|
|
8
8
|
|
9
9
|
module RediSearch
|
10
10
|
class Search
|
11
|
+
extend Forwardable
|
11
12
|
include LazilyLoad
|
12
13
|
include Clauses
|
13
14
|
|
15
|
+
attr_reader :term_clause, :used_clauses, :index, :clauses
|
16
|
+
|
17
|
+
def_delegator :index, :model
|
18
|
+
|
14
19
|
def initialize(index, term = nil, **term_options)
|
15
20
|
@index = index
|
16
21
|
@clauses = []
|
17
22
|
@used_clauses = Set.new
|
18
23
|
|
19
|
-
@term_clause = term
|
24
|
+
@term_clause = term &&
|
20
25
|
And.new(self, term, nil, **term_options)
|
21
26
|
end
|
22
27
|
|
23
28
|
def results
|
24
|
-
if
|
25
|
-
|
29
|
+
if model
|
30
|
+
no_content unless loaded?
|
31
|
+
|
32
|
+
model.where(id: to_a.map(&:document_id_without_index))
|
26
33
|
else
|
27
34
|
to_a
|
28
35
|
end
|
@@ -30,45 +37,28 @@ module RediSearch
|
|
30
37
|
|
31
38
|
def explain
|
32
39
|
RediSearch.client.call!(
|
33
|
-
"EXPLAINCLI", index.name, term_clause
|
40
|
+
"EXPLAINCLI", index.name, term_clause.to_s
|
34
41
|
).join(" ").strip
|
35
42
|
end
|
36
43
|
|
37
|
-
def to_redis
|
38
|
-
command.map do |arg|
|
39
|
-
inspect_command_arg(arg)
|
40
|
-
end.join(" ")
|
41
|
-
end
|
42
|
-
|
43
44
|
def dup
|
44
45
|
self.class.new(index)
|
45
46
|
end
|
46
47
|
|
47
|
-
attr_reader :term_clause
|
48
|
-
|
49
48
|
private
|
50
49
|
|
51
|
-
|
52
|
-
attr_accessor :index, :clauses
|
50
|
+
attr_writer :index, :clauses
|
53
51
|
|
54
52
|
def command
|
55
|
-
["SEARCH", index.name, term_clause, *clauses
|
53
|
+
["SEARCH", index.name, term_clause.to_s, *clauses]
|
56
54
|
end
|
57
55
|
|
58
56
|
def parse_response(response)
|
59
|
-
@documents = Result.new(
|
60
|
-
end
|
61
|
-
|
62
|
-
def inspect_command_arg(arg)
|
63
|
-
if !arg.to_s.starts_with?(/\(-?@/) && arg.to_s.split(/\s|\|/).size > 1
|
64
|
-
arg.inspect
|
65
|
-
else
|
66
|
-
arg
|
67
|
-
end
|
57
|
+
@documents = Result.new(self, response[0], response[1..-1])
|
68
58
|
end
|
69
59
|
|
70
60
|
def valid?
|
71
|
-
term_clause.
|
61
|
+
!term_clause.to_s.empty?
|
72
62
|
end
|
73
63
|
end
|
74
64
|
end
|
@@ -68,15 +68,15 @@ module RediSearch
|
|
68
68
|
def count
|
69
69
|
return to_a.size if loaded?
|
70
70
|
|
71
|
-
call!(
|
72
|
-
"SEARCH", index.name, term_clause, *Limit.new(total: 0).clause
|
71
|
+
RediSearch.client.call!(
|
72
|
+
"SEARCH", index.name, term_clause.to_s, *Limit.new(total: 0).clause
|
73
73
|
).first
|
74
74
|
end
|
75
75
|
|
76
76
|
def where(**condition)
|
77
77
|
@term_clause = Where.new(self, condition, @term_clause)
|
78
78
|
|
79
|
-
if condition.
|
79
|
+
if condition.empty?
|
80
80
|
@term_clause
|
81
81
|
else
|
82
82
|
self
|
@@ -86,7 +86,7 @@ module RediSearch
|
|
86
86
|
def and(new_term = nil, **term_options)
|
87
87
|
@term_clause = And.new(self, new_term, @term_clause, **term_options)
|
88
88
|
|
89
|
-
if new_term.
|
89
|
+
if new_term.nil?
|
90
90
|
@term_clause
|
91
91
|
else
|
92
92
|
self
|
@@ -96,7 +96,7 @@ module RediSearch
|
|
96
96
|
def or(new_term = nil, **term_options)
|
97
97
|
@term_clause = Or.new(self, new_term, @term_clause, **term_options)
|
98
98
|
|
99
|
-
if new_term.
|
99
|
+
if new_term.nil?
|
100
100
|
@term_clause
|
101
101
|
else
|
102
102
|
self
|
@@ -106,8 +106,7 @@ module RediSearch
|
|
106
106
|
private
|
107
107
|
|
108
108
|
def add_to_clause(clause)
|
109
|
-
used_clauses.add(clause.class
|
110
|
-
clauses.push(*clause.clause)
|
109
|
+
clauses.push(*clause.clause) if used_clauses.add?(clause.class)
|
111
110
|
|
112
111
|
self
|
113
112
|
end
|