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
@@ -1,82 +1,53 @@
|
|
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, :add then GREEN
|
43
|
+
when :drop, :del then RED
|
44
|
+
when :get, :mget, :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)
|
@@ -86,7 +57,7 @@ module RediSearch
|
|
86
57
|
end
|
87
58
|
|
88
59
|
def multiword?(string)
|
89
|
-
!string.to_s.
|
60
|
+
!string.to_s.start_with?(/\(-?@/) && string.to_s.split(/\s|\|/).size > 1
|
90
61
|
end
|
91
62
|
|
92
63
|
def prepend_ft?(arg, index)
|
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,47 @@ 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: [], **options)
|
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, **options.deep_merge(replace: { partial: true })
|
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
|
68
|
+
|
69
|
+
def redi_search_delete_document
|
70
|
+
return unless self.class.redi_search_index.exist?
|
76
71
|
|
77
|
-
|
72
|
+
self.class.redi_search_index.del(
|
73
|
+
redi_search_document, delete_document: true
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def redi_search_add_document
|
78
|
+
return unless self.class.redi_search_index.exist?
|
79
|
+
|
80
|
+
self.class.redi_search_index.add(redi_search_document, replace: true)
|
81
|
+
end
|
78
82
|
end
|
79
83
|
end
|
80
84
|
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,22 +8,28 @@ require "redi_search/search/result"
|
|
8
8
|
|
9
9
|
module RediSearch
|
10
10
|
class Search
|
11
|
-
|
11
|
+
extend Forwardable
|
12
12
|
include LazilyLoad
|
13
13
|
include Clauses
|
14
14
|
|
15
|
+
attr_reader :term_clause, :used_clauses, :index, :clauses
|
16
|
+
|
17
|
+
def_delegator :index, :model
|
18
|
+
|
15
19
|
def initialize(index, term = nil, **term_options)
|
16
20
|
@index = index
|
17
21
|
@clauses = []
|
18
22
|
@used_clauses = Set.new
|
19
23
|
|
20
|
-
@term_clause = term
|
24
|
+
@term_clause = term &&
|
21
25
|
And.new(self, term, nil, **term_options)
|
22
26
|
end
|
23
27
|
|
24
28
|
def results
|
25
|
-
if
|
26
|
-
|
29
|
+
if model
|
30
|
+
no_content unless loaded?
|
31
|
+
|
32
|
+
model.where(id: to_a.map(&:document_id_without_index))
|
27
33
|
else
|
28
34
|
to_a
|
29
35
|
end
|
@@ -31,45 +37,28 @@ module RediSearch
|
|
31
37
|
|
32
38
|
def explain
|
33
39
|
RediSearch.client.call!(
|
34
|
-
"EXPLAINCLI", index.name, term_clause
|
40
|
+
"EXPLAINCLI", index.name, term_clause.to_s
|
35
41
|
).join(" ").strip
|
36
42
|
end
|
37
43
|
|
38
|
-
def to_redis
|
39
|
-
command.map do |arg|
|
40
|
-
inspect_command_arg(arg)
|
41
|
-
end.join(" ")
|
42
|
-
end
|
43
|
-
|
44
44
|
def dup
|
45
45
|
self.class.new(index)
|
46
46
|
end
|
47
47
|
|
48
|
-
attr_reader :term_clause
|
49
|
-
|
50
48
|
private
|
51
49
|
|
52
|
-
|
53
|
-
attr_accessor :index, :clauses
|
50
|
+
attr_writer :index, :clauses
|
54
51
|
|
55
52
|
def command
|
56
|
-
["SEARCH", index.name, term_clause, *clauses
|
53
|
+
["SEARCH", index.name, term_clause.to_s, *clauses]
|
57
54
|
end
|
58
55
|
|
59
56
|
def parse_response(response)
|
60
|
-
@documents = Result.new(
|
61
|
-
end
|
62
|
-
|
63
|
-
def inspect_command_arg(arg)
|
64
|
-
if !arg.to_s.starts_with?(/\(-?@/) && arg.to_s.split(/\s|\|/).size > 1
|
65
|
-
arg.inspect
|
66
|
-
else
|
67
|
-
arg
|
68
|
-
end
|
57
|
+
@documents = Result.new(self, response[0], response[1..-1])
|
69
58
|
end
|
70
59
|
|
71
60
|
def valid?
|
72
|
-
term_clause.
|
61
|
+
!term_clause.to_s.empty?
|
73
62
|
end
|
74
63
|
end
|
75
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
|
@@ -1,15 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "redi_search/validatable"
|
4
|
+
|
3
5
|
module RediSearch
|
4
6
|
class Search
|
5
7
|
module Clauses
|
6
8
|
class ApplicationClause
|
7
|
-
include
|
9
|
+
include Validatable
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def clause_term(term, **validations)
|
13
|
+
attr_reader term
|
14
|
+
|
15
|
+
validations.each do |validation_type, options|
|
16
|
+
define_validation(term, validation_type, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
8
21
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
22
|
+
def define_validation(term, type, options)
|
23
|
+
if options.is_a? Hash
|
24
|
+
public_send("validates_#{type}_of", term, **options)
|
25
|
+
else
|
26
|
+
public_send("validates_#{type}_of", term)
|
27
|
+
end
|
13
28
|
end
|
14
29
|
end
|
15
30
|
end
|