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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +20 -0
  3. data/.github/workflows/tests.yml +44 -0
  4. data/.rubocop.yml +67 -189
  5. data/Appraisals +4 -8
  6. data/Gemfile +3 -4
  7. data/README.md +79 -74
  8. data/Rakefile +15 -3
  9. data/bin/console +29 -0
  10. data/gemfiles/{rails_6.gemfile → activerecord_51.gemfile} +1 -5
  11. data/gemfiles/{rails_51.gemfile → activerecord_52.gemfile} +1 -5
  12. data/lib/redi_search.rb +8 -6
  13. data/lib/redi_search/add.rb +9 -5
  14. data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
  15. data/lib/redi_search/client.rb +8 -6
  16. data/lib/redi_search/client/response.rb +8 -8
  17. data/lib/redi_search/configuration.rb +1 -11
  18. data/lib/redi_search/create.rb +6 -4
  19. data/lib/redi_search/document.rb +5 -4
  20. data/lib/redi_search/document/display.rb +9 -9
  21. data/lib/redi_search/document/finder.rb +10 -2
  22. data/lib/redi_search/index.rb +9 -9
  23. data/lib/redi_search/lazily_load.rb +7 -13
  24. data/lib/redi_search/log_subscriber.rb +23 -52
  25. data/lib/redi_search/model.rb +43 -39
  26. data/lib/redi_search/schema.rb +3 -3
  27. data/lib/redi_search/schema/field.rb +2 -3
  28. data/lib/redi_search/schema/tag_field.rb +1 -1
  29. data/lib/redi_search/schema/text_field.rb +1 -1
  30. data/lib/redi_search/search.rb +15 -26
  31. data/lib/redi_search/search/clauses.rb +6 -7
  32. data/lib/redi_search/search/clauses/application_clause.rb +20 -5
  33. data/lib/redi_search/search/clauses/boolean.rb +8 -6
  34. data/lib/redi_search/search/clauses/highlight.rb +18 -2
  35. data/lib/redi_search/search/clauses/limit.rb +7 -5
  36. data/lib/redi_search/search/clauses/return.rb +1 -1
  37. data/lib/redi_search/search/clauses/slop.rb +1 -1
  38. data/lib/redi_search/search/clauses/sort_by.rb +1 -1
  39. data/lib/redi_search/search/clauses/where.rb +13 -3
  40. data/lib/redi_search/search/result.rb +27 -11
  41. data/lib/redi_search/search/term.rb +7 -6
  42. data/lib/redi_search/spellcheck.rb +3 -4
  43. data/lib/redi_search/spellcheck/result.rb +1 -1
  44. data/lib/redi_search/validatable.rb +49 -0
  45. data/lib/redi_search/validations/inclusion.rb +26 -0
  46. data/lib/redi_search/validations/numericality.rb +45 -0
  47. data/lib/redi_search/validations/presence.rb +29 -0
  48. data/lib/redi_search/version.rb +1 -1
  49. data/redi_search.gemspec +1 -3
  50. metadata +14 -45
  51. data/.travis.yml +0 -31
  52. data/bin/test +0 -7
  53. 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[: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/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.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(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
- 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
@@ -11,9 +11,8 @@ module RediSearch
11
11
 
12
12
  def boolean_options_string
13
13
  boolean_options.map do |option|
14
- unless FALSES.include?(send(option))
15
- option.to_s.upcase.split("_").join
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
@@ -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,22 +8,28 @@ require "redi_search/search/result"
8
8
 
9
9
  module RediSearch
10
10
  class Search
11
- include Enumerable
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.presence &&
24
+ @term_clause = term &&
21
25
  And.new(self, term, nil, **term_options)
22
26
  end
23
27
 
24
28
  def results
25
- if index.model.present?
26
- index.model.where(id: to_a.map(&:document_id_without_index))
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
- attr_reader :documents, :used_clauses
53
- attr_accessor :index, :clauses
50
+ attr_writer :index, :clauses
54
51
 
55
52
  def command
56
- ["SEARCH", index.name, term_clause, *clauses.uniq]
53
+ ["SEARCH", index.name, term_clause.to_s, *clauses]
57
54
  end
58
55
 
59
56
  def parse_response(response)
60
- @documents = Result.new(index, used_clauses, response[0], response[1..-1])
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.present?
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.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
@@ -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 ActiveModel::Validations
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
- def self.clause_term(term, *validations)
10
- attr_reader term
11
- validations.each do |validation|
12
- validates term, validation
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