redi_search 1.0.3 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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