redi_search 1.0.5 → 2.0.0

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