redi_search 1.0.5 → 2.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -156
  3. data/.travis.yml +47 -15
  4. data/Appraisals +4 -8
  5. data/Gemfile +3 -4
  6. data/README.md +74 -73
  7. data/Rakefile +15 -3
  8. data/bin/console +36 -0
  9. data/gemfiles/{rails_6.gemfile → activerecord_51.gemfile} +1 -5
  10. data/gemfiles/{rails_51.gemfile → activerecord_52.gemfile} +1 -5
  11. data/lib/redi_search.rb +6 -7
  12. data/lib/redi_search/add.rb +9 -5
  13. data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
  14. data/lib/redi_search/client.rb +8 -6
  15. data/lib/redi_search/client/response.rb +4 -0
  16. data/lib/redi_search/configuration.rb +1 -11
  17. data/lib/redi_search/create.rb +6 -4
  18. data/lib/redi_search/document.rb +5 -4
  19. data/lib/redi_search/document/display.rb +9 -9
  20. data/lib/redi_search/document/finder.rb +10 -2
  21. data/lib/redi_search/index.rb +9 -9
  22. data/lib/redi_search/lazily_load.rb +6 -11
  23. data/lib/redi_search/log_subscriber.rb +23 -52
  24. data/lib/redi_search/model.rb +43 -39
  25. data/lib/redi_search/schema.rb +3 -3
  26. data/lib/redi_search/schema/tag_field.rb +1 -1
  27. data/lib/redi_search/schema/text_field.rb +1 -1
  28. data/lib/redi_search/search.rb +12 -25
  29. data/lib/redi_search/search/clauses.rb +6 -7
  30. data/lib/redi_search/search/clauses/application_clause.rb +20 -5
  31. data/lib/redi_search/search/clauses/boolean.rb +5 -5
  32. data/lib/redi_search/search/clauses/highlight.rb +18 -2
  33. data/lib/redi_search/search/clauses/limit.rb +7 -5
  34. data/lib/redi_search/search/clauses/return.rb +1 -1
  35. data/lib/redi_search/search/clauses/slop.rb +1 -1
  36. data/lib/redi_search/search/clauses/sort_by.rb +1 -1
  37. data/lib/redi_search/search/clauses/where.rb +10 -2
  38. data/lib/redi_search/search/result.rb +9 -9
  39. data/lib/redi_search/search/term.rb +7 -6
  40. data/lib/redi_search/spellcheck.rb +3 -4
  41. data/lib/redi_search/spellcheck/result.rb +1 -1
  42. data/lib/redi_search/validatable.rb +49 -0
  43. data/lib/redi_search/validations/inclusion.rb +26 -0
  44. data/lib/redi_search/validations/numericality.rb +45 -0
  45. data/lib/redi_search/validations/presence.rb +29 -0
  46. data/lib/redi_search/version.rb +1 -1
  47. data/redi_search.gemspec +0 -2
  48. metadata +9 -41
  49. data/bin/test +0 -7
  50. data/gemfiles/rails_52.gemfile +0 -17
@@ -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, :empty?, :[], :last
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!(*command).yield_self do |response|
56
+ call!.yield_self do |response|
60
57
  parse_response(response)
61
58
  end
62
59
  end
63
60
 
64
- def call!(*command)
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,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[:searchkick_runtime] = value
8
+ Thread.current[:redi_search_runtime] = value
7
9
  end
8
10
 
9
11
  def self.runtime
10
- Thread.current[:searchkick_runtime] ||= 0
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 search(event)
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/CyclomaticComplexity, 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/CyclomaticComplexity, 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.starts_with?(/\(-?@/) && string.to_s.split(/\s|\|/).size > 1
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)
@@ -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
- extend ActiveSupport::Concern
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
9
10
 
10
- # rubocop:disable Metrics/BlockLength
11
- class_methods do
11
+ module ClassMethods
12
12
  attr_reader :redi_search_index, :redi_search_serializer
13
13
 
14
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
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
- class << self
28
- def search(term = nil, **term_options)
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, Metrics/AbcSize
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
- 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
- )
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
- def redi_search_delete_document
67
- return unless self.class.redi_search_index.exist?
47
+ def spellcheck(term, distance: 1)
48
+ redi_search_index.spellcheck(term, distance: distance)
49
+ end
68
50
 
69
- self.class.redi_search_index.del(
70
- redi_search_document, delete_document: true
71
- )
51
+ def reindex(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
+ **options.deep_merge(replace: { partial: true })
56
+ )
57
+ end
58
+ end
72
59
  end
73
60
 
74
- def redi_search_add_document
75
- return unless self.class.redi_search_index.exist?
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
- self.class.redi_search_index.add(redi_search_document, replace: true)
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
@@ -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
- constantize.new(field_name, **options.to_h).to_a
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 alter(field_name, options)
32
+ def add_field(field_name, options)
33
33
  raw[field_name] = options
34
34
  self
35
35
  end
@@ -14,8 +14,8 @@ module RediSearch
14
14
 
15
15
  def to_a
16
16
  query = [name.to_s, "TAG"]
17
- query += boolean_options_string
18
17
  query += ["SEPARATOR", separator] if separator
18
+ query += boolean_options_string
19
19
 
20
20
  query
21
21
  end
@@ -15,9 +15,9 @@ module RediSearch
15
15
 
16
16
  def to_a
17
17
  query = [name.to_s, "TEXT"]
18
- query += boolean_options_string
19
18
  query += ["WEIGHT", weight] if weight
20
19
  query += ["PHONETIC", phonetic] if phonetic
20
+ query += boolean_options_string
21
21
 
22
22
  query
23
23
  end
@@ -8,21 +8,25 @@ 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
+ def_delegator :index, :model
17
+
14
18
  def initialize(index, term = nil, **term_options)
15
19
  @index = index
16
20
  @clauses = []
17
21
  @used_clauses = Set.new
18
22
 
19
- @term_clause = term.presence &&
23
+ @term_clause = term &&
20
24
  And.new(self, term, nil, **term_options)
21
25
  end
22
26
 
23
27
  def results
24
- if index.model.present?
25
- index.model.where(id: to_a.map(&:document_id_without_index))
28
+ if model
29
+ model.where(id: to_a.map(&:document_id_without_index))
26
30
  else
27
31
  to_a
28
32
  end
@@ -30,45 +34,28 @@ module RediSearch
30
34
 
31
35
  def explain
32
36
  RediSearch.client.call!(
33
- "EXPLAINCLI", index.name, term_clause
37
+ "EXPLAINCLI", index.name, term_clause.to_s
34
38
  ).join(" ").strip
35
39
  end
36
40
 
37
- def to_redis
38
- command.map do |arg|
39
- inspect_command_arg(arg)
40
- end.join(" ")
41
- end
42
-
43
41
  def dup
44
42
  self.class.new(index)
45
43
  end
46
44
 
47
- attr_reader :term_clause
48
-
49
45
  private
50
46
 
51
- attr_reader :documents, :used_clauses
52
- attr_accessor :index, :clauses
47
+ attr_writer :index, :clauses
53
48
 
54
49
  def command
55
- ["SEARCH", index.name, term_clause, *clauses.uniq]
50
+ ["SEARCH", index.name, term_clause.to_s, *clauses]
56
51
  end
57
52
 
58
53
  def parse_response(response)
59
- @documents = Result.new(index, used_clauses, response[0], response[1..-1])
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
54
+ @documents = Result.new(self, response[0], response[1..-1])
68
55
  end
69
56
 
70
57
  def valid?
71
- term_clause.present?
58
+ !term_clause.to_s.empty?
72
59
  end
73
60
  end
74
61
  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.blank?
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.blank?
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.blank?
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.name.demodulize.underscore)
110
- clauses.push(*clause.clause)
109
+ clauses.push(*clause.clause) if used_clauses.add?(clause.class)
111
110
 
112
111
  self
113
112
  end