redi_search 5.0.0 → 6.0.0

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