redi_search 5.0.0 → 6.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +3 -3
  3. data/.github/workflows/tests.yml +8 -5
  4. data/Gemfile +1 -0
  5. data/README.md +57 -58
  6. data/bin/console +8 -4
  7. data/bin/publish +2 -2
  8. data/gemfiles/activerecord_60.gemfile +1 -0
  9. data/gemfiles/activerecord_61.gemfile +1 -0
  10. data/gemfiles/activerecord_70.gemfile +2 -1
  11. data/lib/redi_search/add_field.rb +9 -15
  12. data/lib/redi_search/client.rb +9 -7
  13. data/lib/redi_search/document.rb +6 -11
  14. data/lib/redi_search/index.rb +4 -11
  15. data/lib/redi_search/model.rb +20 -28
  16. data/lib/redi_search/schema/field.rb +15 -1
  17. data/lib/redi_search/schema/geo_field.rb +5 -6
  18. data/lib/redi_search/schema/numeric_field.rb +5 -6
  19. data/lib/redi_search/schema/tag_field.rb +12 -8
  20. data/lib/redi_search/schema/text_field.rb +7 -6
  21. data/lib/redi_search/schema.rb +33 -25
  22. data/lib/redi_search/search/clauses/and.rb +0 -2
  23. data/lib/redi_search/search/clauses/application_clause.rb +0 -2
  24. data/lib/redi_search/search/clauses/boolean.rb +1 -1
  25. data/lib/redi_search/search/clauses/in_order.rb +0 -2
  26. data/lib/redi_search/search/clauses/language.rb +0 -2
  27. data/lib/redi_search/search/clauses/limit.rb +0 -2
  28. data/lib/redi_search/search/clauses/no_content.rb +0 -2
  29. data/lib/redi_search/search/clauses/no_stop_words.rb +0 -2
  30. data/lib/redi_search/search/clauses/or.rb +0 -2
  31. data/lib/redi_search/search/clauses/return.rb +0 -2
  32. data/lib/redi_search/search/clauses/slop.rb +0 -2
  33. data/lib/redi_search/search/clauses/sort_by.rb +0 -2
  34. data/lib/redi_search/search/clauses/verbatim.rb +0 -2
  35. data/lib/redi_search/search/clauses/where.rb +1 -1
  36. data/lib/redi_search/search/clauses/with_scores.rb +0 -2
  37. data/lib/redi_search/search/clauses.rb +0 -16
  38. data/lib/redi_search/search/term.rb +16 -9
  39. data/lib/redi_search/search.rb +0 -6
  40. data/lib/redi_search/spellcheck.rb +2 -6
  41. data/lib/redi_search/validatable.rb +0 -4
  42. data/lib/redi_search/validations/numericality.rb +0 -2
  43. data/lib/redi_search/version.rb +1 -1
  44. data/lib/redi_search.rb +3 -7
  45. data/redi_search.gemspec +8 -5
  46. metadata +17 -2
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/index"
4
-
5
3
  module RediSearch
6
4
  module Model
7
5
  def self.included(base)
@@ -9,18 +7,16 @@ module RediSearch
9
7
  end
10
8
 
11
9
  module ClassMethods
12
- attr_reader :redi_search_index, :redi_search_serializer
10
+ attr_reader :search_index
13
11
 
14
12
  # rubocop:disable Metrics/MethodLength
15
- def redi_search(schema:, **options)
16
- @redi_search_index = Index.new(
17
- [options[:index_prefix],
18
- model_name.plural, RediSearch.env].compact.join("_"),
19
- schema,
20
- self
13
+ def redi_search(**options, &schema)
14
+ @search_index = Index.new(
15
+ [options[:index_prefix], model_name.plural, RediSearch.env].
16
+ compact.join("_"),
17
+ self, &schema
21
18
  )
22
- @redi_search_serializer = options[:serializer]
23
- register_redi_search_commit_hooks
19
+ register_search_commit_hooks
24
20
 
25
21
  scope :search_import, -> { all }
26
22
 
@@ -31,27 +27,26 @@ module RediSearch
31
27
 
32
28
  private
33
29
 
34
- def register_redi_search_commit_hooks
35
- after_commit(:redi_search_add_document, on: %i(create update)) if
36
- respond_to?(:after_commit)
37
- after_destroy_commit(:redi_search_delete_document) if
30
+ def register_search_commit_hooks
31
+ after_save_commit(:add_to_index) if respond_to?(:after_save_commit)
32
+ after_destroy_commit(:remove_from_index) if
38
33
  respond_to?(:after_destroy_commit)
39
34
  end
40
35
  end
41
36
 
42
37
  module ModelClassMethods
43
38
  def search(term = nil, **term_options)
44
- redi_search_index.search(term, **term_options)
39
+ search_index.search(term, **term_options)
45
40
  end
46
41
 
47
42
  def spellcheck(term, distance: 1)
48
- redi_search_index.spellcheck(term, distance: distance)
43
+ search_index.spellcheck(term, distance: distance)
49
44
  end
50
45
 
51
46
  def reindex(recreate: false, only: [])
52
47
  search_import.find_in_batches.all? do |group|
53
- redi_search_index.reindex(
54
- group.map { |record| record.redi_search_document(only: only) },
48
+ search_index.reindex(
49
+ group.map { |record| record.search_document(only: only) },
55
50
  recreate: recreate
56
51
  )
57
52
  end
@@ -59,19 +54,16 @@ module RediSearch
59
54
  end
60
55
 
61
56
  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
- )
57
+ def search_document(only: [])
58
+ Document.for_object(self.class.search_index, self, only: only)
67
59
  end
68
60
 
69
- def redi_search_delete_document
70
- self.class.redi_search_index.del(redi_search_document)
61
+ def remove_from_index
62
+ self.class.search_index.del(search_document)
71
63
  end
72
64
 
73
- def redi_search_add_document
74
- self.class.redi_search_index.add(redi_search_document)
65
+ def add_to_index
66
+ self.class.search_index.add(search_document)
75
67
  end
76
68
  end
77
69
  end
@@ -7,12 +7,26 @@ module RediSearch
7
7
  @name&.to_sym
8
8
  end
9
9
 
10
- def serialize(value)
10
+ def coerce(value)
11
11
  value
12
12
  end
13
13
 
14
+ def serialize(record)
15
+ if value_block
16
+ record.instance_exec(&value_block)
17
+ else
18
+ record.public_send(name)
19
+ end
20
+ end
21
+
22
+ def tag?
23
+ false
24
+ end
25
+
14
26
  private
15
27
 
28
+ attr_reader :value_block
29
+
16
30
  FALSES = [
17
31
  nil, "", false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"
18
32
  ].freeze
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/schema/field"
4
-
5
3
  module RediSearch
6
4
  class Schema
7
5
  class GeoField < Field
8
- def initialize(name, sortable: false, no_index: false)
9
- @name = name
10
- @sortable = sortable
11
- @no_index = no_index
6
+ def initialize(name, sortable: false, no_index: false, &block)
7
+ @name = name
8
+ @sortable = sortable
9
+ @no_index = no_index
10
+ @value_block = block
12
11
  end
13
12
 
14
13
  def to_a
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/schema/field"
4
-
5
3
  module RediSearch
6
4
  class Schema
7
5
  class NumericField < Field
8
- def initialize(name, sortable: false, no_index: false)
9
- @name = name
10
- @sortable = sortable
11
- @no_index = no_index
6
+ def initialize(name, sortable: false, no_index: false, &block)
7
+ @name = name
8
+ @sortable = sortable
9
+ @no_index = no_index
10
+ @value_block = block
12
11
  end
13
12
 
14
13
  def to_a
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/schema/field"
4
-
5
3
  module RediSearch
6
4
  class Schema
7
5
  class TagField < Field
8
- def initialize(name, separator: ",", sortable: false, no_index: false)
9
- @name = name
10
- @separator = separator
11
- @sortable = sortable
12
- @no_index = no_index
6
+ def initialize(name, separator: ",", sortable: false, no_index: false,
7
+ &block)
8
+ @name = name
9
+ @separator = separator
10
+ @sortable = sortable
11
+ @no_index = no_index
12
+ @value_block = block
13
13
  end
14
14
 
15
15
  def to_a
@@ -20,10 +20,14 @@ module RediSearch
20
20
  query
21
21
  end
22
22
 
23
- def serialize(value)
23
+ def coerce(value)
24
24
  value.join(separator)
25
25
  end
26
26
 
27
+ def tag?
28
+ true
29
+ end
30
+
27
31
  private
28
32
 
29
33
  attr_reader :separator, :sortable, :no_index
@@ -4,13 +4,14 @@ module RediSearch
4
4
  class Schema
5
5
  class TextField < Field
6
6
  def initialize(name, weight: 1.0, phonetic: nil, sortable: false,
7
- no_index: false, no_stem: false)
7
+ no_index: false, no_stem: false, &block)
8
8
  @name = name
9
- @weight = weight
10
- @phonetic = phonetic
11
- @sortable = sortable
12
- @no_index = no_index
13
- @no_stem = no_stem
9
+ @value_block = block
10
+
11
+ { weight: weight, phonetic: phonetic, sortable: sortable,
12
+ no_index: no_index, no_stem: no_stem }.each do |attr, value|
13
+ instance_variable_set("@#{attr}", value)
14
+ end
14
15
  end
15
16
 
16
17
  def to_a
@@ -1,46 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/schema/geo_field"
4
- require "redi_search/schema/numeric_field"
5
- require "redi_search/schema/tag_field"
6
- require "redi_search/schema/text_field"
7
-
8
3
  module RediSearch
9
4
  class Schema
10
- def self.make_field(field_name, options)
11
- options = [options] if options.is_a? Symbol
12
- schema, options = options.to_a.flatten
5
+ attr_reader :fields
6
+
7
+ def initialize(&block)
8
+ @fields = []
13
9
 
14
- Object.const_get("RediSearch::Schema::#{schema.to_s.capitalize}Field").
15
- new(field_name, **options.to_h)
10
+ instance_exec(&block)
16
11
  end
17
12
 
18
- def initialize(raw)
19
- @raw = raw
13
+ def text_field(name, **options, &block)
14
+ self[name] || push(Schema::TextField.new(name, **options, &block))
20
15
  end
21
16
 
22
- def to_a
23
- fields.map(&:to_a).flatten
17
+ def numeric_field(name, **options, &block)
18
+ self[name] || push(Schema::NumericField.new(name, **options, &block))
19
+ end
20
+
21
+ def tag_field(name, **options, &block)
22
+ self[name] || push(Schema::TagField.new(name, **options, &block))
24
23
  end
25
24
 
26
- def [](field)
27
- fields.group_by(&:name)[field]&.first
25
+ def geo_field(name, **options, &block)
26
+ self[name] || push(Schema::GeoField.new(name, **options, &block))
28
27
  end
29
28
 
30
- def fields
31
- @fields ||= raw.map do |field_name, options|
32
- self.class.make_field(field_name, options)
33
- end.flatten
29
+ def add_field(name, type, **options, &block)
30
+ case type
31
+ when :text then method(:text_field)
32
+ when :numeric then method(:numeric_field)
33
+ when :tag then method(:tag_field)
34
+ when :geo then method(:geo_field)
35
+ end.call(name, **options, &block)
34
36
  end
35
37
 
36
- def add_field(field_name, options)
37
- raw[field_name] = options
38
- @fields = nil
39
- self
38
+ def to_a
39
+ fields.map(&:to_a).flatten
40
+ end
41
+
42
+ def [](name)
43
+ fields.find { |field| field.name == name }
40
44
  end
41
45
 
42
46
  private
43
47
 
44
- attr_accessor :raw
48
+ def push(field)
49
+ @fields.push(field)
50
+
51
+ field
52
+ end
45
53
  end
46
54
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/boolean"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/validatable"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -48,7 +48,7 @@ module RediSearch
48
48
  @term = if term.is_a? RediSearch::Search
49
49
  term
50
50
  else
51
- Term.new(term, **term_options)
51
+ Term.new(term, nil, **term_options)
52
52
  end
53
53
  end
54
54
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/boolean"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -62,7 +62,7 @@ module RediSearch
62
62
  if condition[1].is_a? RediSearch::Search
63
63
  condition[1]
64
64
  else
65
- Term.new(condition[1], **options.to_h)
65
+ Term.new(condition[1], search.index.schema[@field], **options.to_h)
66
66
  end
67
67
  end
68
68
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/clauses/application_clause"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  module Clauses
@@ -1,21 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/search/term"
4
- require "redi_search/search/clauses/slop"
5
- require "redi_search/search/clauses/in_order"
6
- require "redi_search/search/clauses/language"
7
- require "redi_search/search/clauses/sort_by"
8
- require "redi_search/search/clauses/limit"
9
- require "redi_search/search/clauses/no_content"
10
- require "redi_search/search/clauses/verbatim"
11
- require "redi_search/search/clauses/no_stop_words"
12
- require "redi_search/search/clauses/return"
13
- require "redi_search/search/clauses/with_scores"
14
- require "redi_search/search/clauses/highlight"
15
- require "redi_search/search/clauses/and"
16
- require "redi_search/search/clauses/or"
17
- require "redi_search/search/clauses/where"
18
-
19
3
  module RediSearch
20
4
  class Search
21
5
  module Clauses
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/validatable"
4
-
5
3
  module RediSearch
6
4
  class Search
7
5
  class Term
@@ -12,16 +10,17 @@ module RediSearch
12
10
  validates_inclusion_of :option, within: %i(fuzziness optional prefix),
13
11
  allow_nil: true
14
12
 
15
- def initialize(term, **options)
16
- @term = term
13
+ def initialize(term, field = nil, **options)
14
+ @term = term
15
+ @field = field
17
16
  @options = options
18
17
 
19
18
  validate!
20
19
  end
21
20
 
22
21
  def to_s
23
- if @term.is_a? Range
24
- stringify_range
22
+ if term.is_a?(Range) then stringify_range
23
+ elsif !field.nil? && field.tag? then stringify_tag
25
24
  else
26
25
  stringify_query
27
26
  end
@@ -29,7 +28,7 @@ module RediSearch
29
28
 
30
29
  private
31
30
 
32
- attr_accessor :term, :options
31
+ attr_accessor :term, :field, :options
33
32
 
34
33
  def fuzziness
35
34
  @fuzziness ||= options[:fuzziness]
@@ -52,7 +51,7 @@ module RediSearch
52
51
  end
53
52
 
54
53
  def stringify_query
55
- @term.to_s.
54
+ term.to_s.
56
55
  tr("`", "\`").
57
56
  yield_self { |str| "#{fuzzy_operator}#{str}#{fuzzy_operator}" }.
58
57
  yield_self { |str| "#{optional_operator}#{str}" }.
@@ -60,8 +59,16 @@ module RediSearch
60
59
  yield_self { |str| "`#{str}`" }
61
60
  end
62
61
 
62
+ def stringify_tag
63
+ if term.is_a?(Array)
64
+ "{ #{term.join(' | ')} }"
65
+ else
66
+ "{ #{term} }"
67
+ end
68
+ end
69
+
63
70
  def stringify_range
64
- first, last = @term.first, @term.last
71
+ first, last = term.first, term.last
65
72
  first = "-inf" if first == -Float::INFINITY
66
73
  last = "+inf" if last == Float::INFINITY
67
74
 
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/lazily_load"
4
-
5
- require "redi_search/search/clauses"
6
- require "redi_search/search/term"
7
- require "redi_search/search/result"
8
-
9
3
  module RediSearch
10
4
  class Search
11
5
  extend Forwardable
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/lazily_load"
4
- require "redi_search/spellcheck/result"
5
- require "redi_search/validatable"
6
-
7
3
  module RediSearch
8
4
  class Spellcheck
9
5
  include LazilyLoad
@@ -41,9 +37,9 @@ module RediSearch
41
37
  end
42
38
 
43
39
  def parse_response(response)
44
- suggestions = response.map do |suggestion|
40
+ suggestions = response.to_h do |suggestion|
45
41
  suggestion[1..2]
46
- end.to_h
42
+ end
47
43
 
48
44
  @documents = parsed_terms.map do |term|
49
45
  Result.new(term, suggestions[term] || [])
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/validations/inclusion"
4
- require "redi_search/validations/presence"
5
- require "redi_search/validations/numericality"
6
-
7
3
  module RediSearch
8
4
  class ValidationError < StandardError
9
5
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/validations/inclusion"
4
-
5
3
  module RediSearch
6
4
  module Validations
7
5
  class Numericality
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RediSearch
4
- VERSION = "5.0.0"
4
+ VERSION = "6.0.0"
5
5
  end
data/lib/redi_search.rb CHANGED
@@ -4,14 +4,10 @@ require "delegate"
4
4
  require "forwardable"
5
5
  require "redis"
6
6
  require "active_support/lazy_load_hooks"
7
+ require "zeitwerk"
7
8
 
8
- require "redi_search/configuration"
9
- require "redi_search/client"
10
-
11
- require "redi_search/model"
12
- require "redi_search/index"
13
- require "redi_search/log_subscriber"
14
- require "redi_search/document"
9
+ loader = Zeitwerk::Loader.for_gem
10
+ loader.setup
15
11
 
16
12
  module RediSearch
17
13
  class << self
data/redi_search.gemspec CHANGED
@@ -18,16 +18,19 @@ Gem::Specification.new do |spec|
18
18
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test)/}) }
19
19
  end
20
20
 
21
- spec.metadata["github_repo"] = "ssh://github.com/npezza93/redi_search"
22
- spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["source_code_uri"] = spec.homepage
24
- spec.metadata["changelog_uri"] =
25
- "https://github.com/npezza93/redi_search/releases"
21
+ spec.metadata = {
22
+ "rubygems_mfa_required" => "true",
23
+ "github_repo" => "ssh://github.com/npezza93/redi_search",
24
+ "homepage_uri" => spec.homepage,
25
+ "source_code_uri" => spec.homepage,
26
+ "changelog_uri" => "https://github.com/npezza93/redi_search/releases",
27
+ }
26
28
 
27
29
  spec.required_ruby_version = ">= 2.7.0"
28
30
 
29
31
  spec.add_runtime_dependency "activesupport", ">= 5.1", "< 7.1"
30
32
  spec.add_runtime_dependency "redis", ">= 4.0", "< 5.0"
33
+ spec.add_runtime_dependency "zeitwerk"
31
34
 
32
35
  spec.add_development_dependency "bundler", ">= 1.17", "< 3"
33
36
  spec.add_development_dependency "minitest", "~> 5.0"