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
@@ -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
@@ -11,13 +11,13 @@ module RediSearch
11
11
  @prior_clause = prior_clause
12
12
  @not = false
13
13
 
14
- initialize_term(term, **term_options) if term.present?
14
+ initialize_term(term, **term_options) if term
15
15
  end
16
16
 
17
17
  def to_s
18
- raise ArgumentError, "missing query terms" if term.blank?
18
+ raise ArgumentError, "missing query terms" unless term
19
19
 
20
- [prior_clause.presence, queryify_term].compact.join(operand)
20
+ [prior_clause, queryify_term].compact.join(operand)
21
21
  end
22
22
 
23
23
  def_delegator :to_s, :inspect
@@ -25,7 +25,7 @@ module RediSearch
25
25
  def not(term, **term_options)
26
26
  @not = true
27
27
 
28
- initialize_term(term, **term_options) if term.present?
28
+ initialize_term(term, **term_options) if term
29
29
 
30
30
  search
31
31
  end
@@ -35,7 +35,7 @@ module RediSearch
35
35
  attr_reader :prior_clause, :term, :search
36
36
 
37
37
  def operand
38
- raise NotImplementedError
38
+ raise NotImplementedError, "#{__method__} needs to be defined"
39
39
  end
40
40
 
41
41
  def not_operator
@@ -23,9 +23,9 @@ module RediSearch
23
23
  attr_reader :fields, :opening_tag, :closing_tag
24
24
 
25
25
  def tags_clause
26
- return if opening_tag.blank? && closing_tag.blank?
26
+ return if !opening_tag? && !closing_tag?
27
27
 
28
- if opening_tag.present? && closing_tag.present?
28
+ if opening_tag? && closing_tag?
29
29
  ["TAGS", opening_tag, closing_tag]
30
30
  else
31
31
  arg_error("Missing opening or closing tag")
@@ -41,6 +41,22 @@ module RediSearch
41
41
  def arg_error(msg)
42
42
  raise ArgumentError, "Highlight: #{msg}"
43
43
  end
44
+
45
+ def opening_tag?
46
+ if opening_tag.respond_to? :empty?
47
+ !opening_tag.empty?
48
+ else
49
+ opening_tag
50
+ end
51
+ end
52
+
53
+ def closing_tag?
54
+ if closing_tag.respond_to? :empty?
55
+ !closing_tag.empty?
56
+ else
57
+ closing_tag
58
+ end
59
+ end
44
60
  end
45
61
  end
46
62
  end
@@ -6,10 +6,12 @@ module RediSearch
6
6
  class Search
7
7
  module Clauses
8
8
  class Limit < ApplicationClause
9
- clause_term :total, presence: true,
10
- numericality: { greater_than_or_equal_to: 0 }
11
- clause_term :offset, presence: true,
12
- numericality: { greater_than_or_equal_to: 0 }
9
+ clause_term :total, presence: true, numericality: {
10
+ within: 0..Float::INFINITY, only_integer: true
11
+ }
12
+ clause_term :offset, presence: true, numericality: {
13
+ within: 0..Float::INFINITY, only_integer: true
14
+ }
13
15
 
14
16
  def initialize(total:, offset: 0)
15
17
  @total = total
@@ -19,7 +21,7 @@ module RediSearch
19
21
  def clause
20
22
  validate!
21
23
 
22
- ["LIMIT", [offset, total]]
24
+ ["LIMIT", offset, total]
23
25
  end
24
26
  end
25
27
  end
@@ -15,7 +15,7 @@ module RediSearch
15
15
  def clause
16
16
  validate!
17
17
 
18
- ["RETURN", fields.size, fields]
18
+ ["RETURN", fields.size, *fields]
19
19
  end
20
20
  end
21
21
  end
@@ -6,7 +6,7 @@ module RediSearch
6
6
  class Search
7
7
  module Clauses
8
8
  class Slop < ApplicationClause
9
- clause_term :slop, numericality: { greater_than_or_equal_to: 0 }
9
+ clause_term :slop, numericality: { within: 0..Float::INFINITY }
10
10
 
11
11
  def initialize(slop:)
12
12
  @slop = slop
@@ -7,7 +7,7 @@ module RediSearch
7
7
  module Clauses
8
8
  class SortBy < ApplicationClause
9
9
  clause_term :field, presence: true
10
- clause_term :order, presence: true, inclusion: { in: %i(asc desc) }
10
+ clause_term :order, presence: true, inclusion: { within: %i(asc desc) }
11
11
 
12
12
  def initialize(field:, order: :asc)
13
13
  @field = field
@@ -16,7 +16,7 @@ module RediSearch
16
16
 
17
17
  def to_s
18
18
  [
19
- prior_clause.presence,
19
+ prior_clause,
20
20
  "(#{not_operator}@#{field}:#{queryify_term})"
21
21
  ].compact.join(" ")
22
22
  end
@@ -50,7 +50,7 @@ module RediSearch
50
50
  end
51
51
 
52
52
  def initialize_term(condition)
53
- return if condition.blank?
53
+ return if condition?(condition)
54
54
 
55
55
  condition, *options = condition.to_a
56
56
 
@@ -65,6 +65,14 @@ module RediSearch
65
65
  Term.new(condition[1], **options.to_h)
66
66
  end
67
67
  end
68
+
69
+ def condition?(condition)
70
+ if condition.respond_to?(:empty?)
71
+ condition.empty?
72
+ else
73
+ !condition
74
+ end
75
+ end
68
76
  end
69
77
  end
70
78
  end
@@ -6,10 +6,10 @@ module RediSearch
6
6
  extend Forwardable
7
7
  include Enumerable
8
8
 
9
- def initialize(index, used_clauses, count, documents)
9
+ def initialize(search, count, documents)
10
10
  @count = count
11
- @used_clauses = used_clauses
12
- @results = parse_results(index, documents)
11
+ @search = search
12
+ @results = parse_results(documents)
13
13
  end
14
14
 
15
15
  def count
@@ -22,11 +22,11 @@ module RediSearch
22
22
 
23
23
  def_delegators :results, :each, :empty?, :[], :last
24
24
 
25
- #:nocov:
26
25
  def inspect
27
26
  results
28
27
  end
29
28
 
29
+ #:nocov:
30
30
  def pretty_print(printer)
31
31
  printer.pp(results)
32
32
  end
@@ -34,7 +34,7 @@ module RediSearch
34
34
 
35
35
  private
36
36
 
37
- attr_reader :results
37
+ attr_reader :results, :search
38
38
 
39
39
  def response_slice
40
40
  slice_length = 2
@@ -45,20 +45,20 @@ module RediSearch
45
45
  end
46
46
 
47
47
  def with_scores?
48
- @used_clauses.include? "with_scores"
48
+ search.used_clauses.include? Search::Clauses::WithScores
49
49
  end
50
50
 
51
51
  def no_content?
52
- @used_clauses.include? "no_content"
52
+ search.used_clauses.include? Search::Clauses::NoContent
53
53
  end
54
54
 
55
- def parse_results(index, documents)
55
+ def parse_results(documents)
56
56
  documents.each_slice(response_slice).map do |slice|
57
57
  document_id = slice[0]
58
58
  fields = slice.last unless no_content?
59
59
  score = slice[1].to_f if with_scores?
60
60
 
61
- Document.new(index, document_id, Hash[*fields.to_a], score)
61
+ Document.new(search.index, document_id, Hash[*fields.to_a], score)
62
62
  end
63
63
  end
64
64
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "redi_search/validatable"
4
+
3
5
  module RediSearch
4
6
  class Search
5
7
  class Term
6
- include ActiveModel::Validations
8
+ include Validatable
7
9
 
8
- validates :fuzziness, numericality: {
9
- only_integer: true, less_than: 4, greater_than: 0, allow_blank: true
10
- }
11
- validates :option, inclusion: { in: %i(fuzziness optional prefix) },
12
- allow_nil: true
10
+ validates_numericality_of :fuzziness, within: 1..3, only_integer: true,
11
+ allow_nil: true
12
+ validates_inclusion_of :option, within: %i(fuzziness optional prefix),
13
+ allow_nil: true
13
14
 
14
15
  def initialize(term, **options)
15
16
  @term = term
@@ -2,15 +2,14 @@
2
2
 
3
3
  require "redi_search/lazily_load"
4
4
  require "redi_search/spellcheck/result"
5
+ require "redi_search/validatable"
5
6
 
6
7
  module RediSearch
7
8
  class Spellcheck
8
9
  include LazilyLoad
9
- include ActiveModel::Validations
10
+ include Validatable
10
11
 
11
- validates :distance, numericality: {
12
- greater_than: 0, less_than: 5
13
- }
12
+ validates_numericality_of :distance, within: 1..4, only_integer: true
14
13
 
15
14
  def initialize(index, terms, distance: 1)
16
15
  @index = index
@@ -14,7 +14,6 @@ module RediSearch
14
14
  end
15
15
  end
16
16
 
17
- #:nocov:
18
17
  def inspect
19
18
  inspection = %w(term suggestions).map do |field_name|
20
19
  "#{field_name}: #{public_send(field_name)}"
@@ -23,6 +22,7 @@ module RediSearch
23
22
  "#<#{self.class} #{inspection}>"
24
23
  end
25
24
 
25
+ #:nocov:
26
26
  def pretty_print(printer) # rubocop:disable Metrics/MethodLength
27
27
  printer.object_address_group(self) do
28
28
  printer.seplist(
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/validations/inclusion"
4
+ require "redi_search/validations/presence"
5
+ require "redi_search/validations/numericality"
6
+
7
+ module RediSearch
8
+ class ValidationError < StandardError
9
+ end
10
+
11
+ module Validatable
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ attr_accessor :validations
18
+
19
+ def validates_inclusion_of(field, within:, **options)
20
+ self.validations = [
21
+ *validations.to_a,
22
+ Validations::Inclusion.new(field: field, within: within, **options)
23
+ ]
24
+ end
25
+
26
+ def validates_presence_of(field)
27
+ self.validations = [
28
+ *validations.to_a,
29
+ Validations::Presence.new(field: field)
30
+ ]
31
+ end
32
+
33
+ def validates_numericality_of(field, within:, **options)
34
+ self.validations = [
35
+ *validations.to_a,
36
+ Validations::Numericality.new(
37
+ field: field, within: within, **options
38
+ )
39
+ ]
40
+ end
41
+ end
42
+
43
+ def validate!
44
+ self.class.validations.to_a.each do |validator|
45
+ validator.validate!(self)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ module Validations
5
+ class Inclusion
6
+ def initialize(field:, within:, allow_nil: false)
7
+ @field = field
8
+ @within = within
9
+ @allow_nil = allow_nil
10
+ end
11
+
12
+ def validate!(object)
13
+ value = object.send(field)
14
+
15
+ return true if within.include?(value) || (allow_nil? && value.nil?)
16
+
17
+ raise ValidationError, "#{value.inspect} not included in #{within}"
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :field, :within, :allow_nil
23
+ alias allow_nil? allow_nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/validations/inclusion"
4
+
5
+ module RediSearch
6
+ module Validations
7
+ class Numericality
8
+ def initialize(field:, within:, only_integer: false, allow_nil: false)
9
+ @field = field
10
+ @within = within
11
+ @only_integer = only_integer
12
+ @allow_nil = allow_nil
13
+ end
14
+
15
+ def validate!(object)
16
+ value = object.send(field)
17
+
18
+ return true if value.nil? && allow_nil?
19
+
20
+ validate_numberness!(value)
21
+ validate_inclusion!(object)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :field, :within, :only_integer, :allow_nil
27
+ alias only_integer? only_integer
28
+ alias allow_nil? allow_nil
29
+
30
+ def validate_numberness!(value)
31
+ raise(ValidationError, "#{field} must be a number") unless
32
+ value.is_a?(Numeric)
33
+
34
+ raise(ValidationError, "#{field} must be an Integer") if
35
+ only_integer? && !value.is_a?(Integer)
36
+
37
+ true
38
+ end
39
+
40
+ def validate_inclusion!(object)
41
+ Inclusion.new(field: field, within: within).validate!(object)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ module Validations
5
+ class Presence
6
+ def initialize(field:)
7
+ @field = field
8
+ end
9
+
10
+ def validate!(object)
11
+ return true if value_present?(object.send(field))
12
+
13
+ raise RediSearch::ValidationError, "#{field} can't be blank"
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :field
19
+
20
+ def value_present?(value)
21
+ if value.respond_to?(:empty?)
22
+ !value.empty?
23
+ else
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end