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
@@ -4,26 +4,28 @@ module RediSearch
4
4
  class Search
5
5
  module Clauses
6
6
  class Boolean
7
+ extend Forwardable
8
+
7
9
  def initialize(search, term, prior_clause = nil, **term_options)
8
10
  @search = search
9
11
  @prior_clause = prior_clause
10
12
  @not = false
11
13
 
12
- initialize_term(term, **term_options) if term.present?
14
+ initialize_term(term, **term_options) if term
13
15
  end
14
16
 
15
17
  def to_s
16
- raise ArgumentError, "missing query terms" if term.blank?
18
+ raise ArgumentError, "missing query terms" unless term
17
19
 
18
- [prior_clause.presence, queryify_term].compact.join(operand)
20
+ [prior_clause, queryify_term].compact.join(operand)
19
21
  end
20
22
 
21
- delegate :inspect, to: :to_s
23
+ def_delegator :to_s, :inspect
22
24
 
23
25
  def not(term, **term_options)
24
26
  @not = true
25
27
 
26
- initialize_term(term, **term_options) if term.present?
28
+ initialize_term(term, **term_options) if term
27
29
 
28
30
  search
29
31
  end
@@ -33,7 +35,7 @@ module RediSearch
33
35
  attr_reader :prior_clause, :term, :search
34
36
 
35
37
  def operand
36
- raise NotImplementedError
38
+ raise NotImplementedError, "#{__method__} needs to be defined"
37
39
  end
38
40
 
39
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
@@ -4,6 +4,8 @@ module RediSearch
4
4
  class Search
5
5
  module Clauses
6
6
  class Where
7
+ extend Forwardable
8
+
7
9
  def initialize(search, condition, prior_clause = nil)
8
10
  @search = search
9
11
  @prior_clause = prior_clause
@@ -14,12 +16,12 @@ module RediSearch
14
16
 
15
17
  def to_s
16
18
  [
17
- prior_clause.presence,
19
+ prior_clause,
18
20
  "(#{not_operator}@#{field}:#{queryify_term})"
19
21
  ].compact.join(" ")
20
22
  end
21
23
 
22
- delegate :inspect, to: :to_s
24
+ def_delegator :to_s, :inspect
23
25
 
24
26
  def not(condition)
25
27
  @not = true
@@ -48,7 +50,7 @@ module RediSearch
48
50
  end
49
51
 
50
52
  def initialize_term(condition)
51
- return if condition.blank?
53
+ return if condition?(condition)
52
54
 
53
55
  condition, *options = condition.to_a
54
56
 
@@ -63,6 +65,14 @@ module RediSearch
63
65
  Term.new(condition[1], **options.to_h)
64
66
  end
65
67
  end
68
+
69
+ def condition?(condition)
70
+ if condition.respond_to?(:empty?)
71
+ condition.empty?
72
+ else
73
+ !condition
74
+ end
75
+ end
66
76
  end
67
77
  end
68
78
  end
@@ -2,24 +2,40 @@
2
2
 
3
3
  module RediSearch
4
4
  class Search
5
- class Result < Array
6
- def initialize(index, used_clauses, count, documents)
7
- @count = count
8
- @used_clauses = used_clauses
5
+ class Result
6
+ extend Forwardable
7
+ include Enumerable
9
8
 
10
- super(parse_results(index, documents))
9
+ def initialize(search, count, documents)
10
+ @count = count
11
+ @search = search
12
+ @results = parse_results(documents)
11
13
  end
12
14
 
13
15
  def count
14
- @count || super
16
+ @count || results.count
15
17
  end
16
18
 
17
19
  def size
18
- @count || super
20
+ @count || results.size
21
+ end
22
+
23
+ def_delegators :results, :each, :empty?, :[], :last
24
+
25
+ def inspect
26
+ results
27
+ end
28
+
29
+ #:nocov:
30
+ def pretty_print(printer)
31
+ printer.pp(results)
19
32
  end
33
+ #:nocov:
20
34
 
21
35
  private
22
36
 
37
+ attr_reader :results, :search
38
+
23
39
  def response_slice
24
40
  slice_length = 2
25
41
  slice_length -= 1 if no_content?
@@ -29,20 +45,20 @@ module RediSearch
29
45
  end
30
46
 
31
47
  def with_scores?
32
- @used_clauses.include? "with_scores"
48
+ search.used_clauses.include? Search::Clauses::WithScores
33
49
  end
34
50
 
35
51
  def no_content?
36
- @used_clauses.include? "no_content"
52
+ search.used_clauses.include? Search::Clauses::NoContent
37
53
  end
38
54
 
39
- def parse_results(index, documents)
55
+ def parse_results(documents)
40
56
  documents.each_slice(response_slice).map do |slice|
41
57
  document_id = slice[0]
42
58
  fields = slice.last unless no_content?
43
59
  score = slice[1].to_f if with_scores?
44
60
 
45
- Document.new(index, document_id, Hash[*fields.to_a], score)
61
+ Document.new(search.index, document_id, Hash[*fields.to_a], score)
46
62
  end
47
63
  end
48
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