redi_search 3.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +8 -4
  3. data/.github/workflows/tests.yml +13 -6
  4. data/.rubocop.yml +2 -1
  5. data/Appraisals +6 -6
  6. data/Gemfile +3 -2
  7. data/README.md +57 -58
  8. data/Rakefile +1 -0
  9. data/bin/console +14 -6
  10. data/bin/publish +2 -2
  11. data/gemfiles/{activerecord_52.gemfile → activerecord_60.gemfile} +2 -2
  12. data/gemfiles/activerecord_61.gemfile +2 -2
  13. data/gemfiles/{activerecord_51.gemfile → activerecord_70.gemfile} +2 -2
  14. data/lib/redi_search/add_field.rb +9 -15
  15. data/lib/redi_search/client.rb +10 -8
  16. data/lib/redi_search/document/display.rb +4 -4
  17. data/lib/redi_search/document.rb +12 -13
  18. data/lib/redi_search/index.rb +5 -12
  19. data/lib/redi_search/lazily_load.rb +2 -2
  20. data/lib/redi_search/log_subscriber.rb +2 -2
  21. data/lib/redi_search/model.rb +20 -28
  22. data/lib/redi_search/schema/field.rb +24 -2
  23. data/lib/redi_search/schema/geo_field.rb +6 -7
  24. data/lib/redi_search/schema/numeric_field.rb +6 -7
  25. data/lib/redi_search/schema/tag_field.rb +16 -8
  26. data/lib/redi_search/schema/text_field.rb +8 -7
  27. data/lib/redi_search/schema.rb +35 -22
  28. data/lib/redi_search/search/clauses/and.rb +0 -2
  29. data/lib/redi_search/search/clauses/application_clause.rb +0 -2
  30. data/lib/redi_search/search/clauses/boolean.rb +1 -1
  31. data/lib/redi_search/search/clauses/in_order.rb +0 -2
  32. data/lib/redi_search/search/clauses/language.rb +0 -2
  33. data/lib/redi_search/search/clauses/limit.rb +0 -2
  34. data/lib/redi_search/search/clauses/no_content.rb +0 -2
  35. data/lib/redi_search/search/clauses/no_stop_words.rb +0 -2
  36. data/lib/redi_search/search/clauses/or.rb +0 -2
  37. data/lib/redi_search/search/clauses/return.rb +0 -2
  38. data/lib/redi_search/search/clauses/slop.rb +0 -2
  39. data/lib/redi_search/search/clauses/sort_by.rb +0 -2
  40. data/lib/redi_search/search/clauses/verbatim.rb +0 -2
  41. data/lib/redi_search/search/clauses/where.rb +1 -1
  42. data/lib/redi_search/search/clauses/with_scores.rb +0 -2
  43. data/lib/redi_search/search/clauses.rb +0 -16
  44. data/lib/redi_search/search/result.rb +2 -2
  45. data/lib/redi_search/search/term.rb +16 -9
  46. data/lib/redi_search/search.rb +1 -7
  47. data/lib/redi_search/spellcheck/result.rb +4 -4
  48. data/lib/redi_search/spellcheck.rb +3 -7
  49. data/lib/redi_search/validatable.rb +0 -4
  50. data/lib/redi_search/validations/numericality.rb +0 -2
  51. data/lib/redi_search/version.rb +1 -1
  52. data/lib/redi_search.rb +3 -7
  53. data/redi_search.gemspec +11 -8
  54. metadata +24 -9
@@ -4,9 +4,9 @@ module RediSearch
4
4
  class Document
5
5
  module Display
6
6
  def inspect
7
- inspection = pretty_print_attributes.map do |field_name|
7
+ inspection = pretty_print_attributes.filter_map do |field_name|
8
8
  "#{field_name}: #{public_send(field_name)}"
9
- end.compact.join(", ")
9
+ end.join(", ")
10
10
 
11
11
  "#<#{self.class} #{inspection}>"
12
12
  end
@@ -19,7 +19,7 @@ module RediSearch
19
19
  pp_attrs.compact
20
20
  end
21
21
 
22
- #:nocov:
22
+ # :nocov:
23
23
  def pretty_print(printer) # rubocop:disable Metrics/MethodLength
24
24
  printer.object_address_group(self) do
25
25
  printer.seplist(
@@ -35,7 +35,7 @@ module RediSearch
35
35
  end
36
36
  end
37
37
  end
38
- #:nocov:
38
+ # :nocov:
39
39
  end
40
40
  end
41
41
  end
@@ -1,23 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/document/display"
4
- require "redi_search/document/finder"
5
-
6
3
  module RediSearch
7
4
  class Document
8
5
  include Display
9
6
 
10
7
  class << self
11
- def for_object(index, record, serializer: nil, only: [])
12
- object_to_serialize = serializer&.new(record) || record
13
-
14
- field_values = index.schema.fields.map do |field|
15
- next unless only.empty? || only.include?(field.to_sym)
8
+ def for_object(index, record, only: [])
9
+ field_values = index.schema.fields.filter_map do |field|
10
+ next unless only.empty? || only.include?(field.name)
16
11
 
17
- [field.to_s, object_to_serialize.public_send(field)]
18
- end.compact.to_h
12
+ [field.name.to_s, field.serialize(record)]
13
+ end.to_h
19
14
 
20
- new(index, object_to_serialize.id, field_values)
15
+ new(index, record.id, field_values)
21
16
  end
22
17
 
23
18
  def get(index, document_id)
@@ -41,11 +36,15 @@ module RediSearch
41
36
  end
42
37
 
43
38
  def schema_fields
44
- @schema_fields ||= index.schema.fields.map(&:to_s)
39
+ @schema_fields ||= index.schema.fields.map do |field|
40
+ field.name.to_s
41
+ end
45
42
  end
46
43
 
47
44
  def redis_attributes
48
- attributes.to_a.flatten
45
+ attributes.flat_map do |field, value|
46
+ [field, index.schema[field.to_sym].coerce(value)]
47
+ end
49
48
  end
50
49
 
51
50
  def document_id
@@ -1,19 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/hset"
4
- require "redi_search/create"
5
- require "redi_search/schema"
6
- require "redi_search/search"
7
- require "redi_search/spellcheck"
8
- require "redi_search/add_field"
9
-
10
3
  module RediSearch
11
4
  class Index
12
5
  attr_reader :name, :schema, :model
13
6
 
14
- def initialize(name, schema, model = nil)
7
+ def initialize(name, model = nil, &schema)
15
8
  @name = name.to_s
16
- @schema = Schema.new(schema)
9
+ @schema = Schema.new(&schema)
17
10
  @model = model
18
11
  end
19
12
 
@@ -80,7 +73,7 @@ module RediSearch
80
73
  end
81
74
 
82
75
  def fields
83
- schema.fields.map(&:to_s)
76
+ schema.fields.map { |field| field.name.to_s }
84
77
  end
85
78
 
86
79
  def reindex(documents, recreate: false)
@@ -94,8 +87,8 @@ module RediSearch
94
87
  info.num_docs.to_i
95
88
  end
96
89
 
97
- def add_field(field_name, schema)
98
- AddField.new(self, field_name, schema).call!
90
+ def add_field(name, type, **options, &block)
91
+ AddField.new(self, name, type, **options, &block).call!
99
92
  end
100
93
 
101
94
  private
@@ -30,7 +30,7 @@ module RediSearch
30
30
  end
31
31
  end
32
32
 
33
- #:nocov:
33
+ # :nocov:
34
34
  def pretty_print(printer)
35
35
  execute_and_rescue_inspection do
36
36
  return super(inspect) unless valid?
@@ -38,7 +38,7 @@ module RediSearch
38
38
  printer.pp(documents)
39
39
  end
40
40
  end
41
- #:nocov:
41
+ # :nocov:
42
42
 
43
43
  private
44
44
 
@@ -12,12 +12,12 @@ module RediSearch
12
12
  Thread.current[:redi_search_runtime] ||= 0
13
13
  end
14
14
 
15
- #:nocov:
15
+ # :nocov:
16
16
  def self.reset_runtime
17
17
  rt, self.runtime = runtime, 0
18
18
  rt
19
19
  end
20
- #:nocov:
20
+ # :nocov:
21
21
 
22
22
  def action(event)
23
23
  self.class.runtime += event.duration
@@ -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
@@ -3,17 +3,39 @@
3
3
  module RediSearch
4
4
  class Schema
5
5
  class Field
6
+ def name
7
+ @name&.to_sym
8
+ end
9
+
10
+ def coerce(value)
11
+ value
12
+ end
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
+
6
26
  private
7
27
 
28
+ attr_reader :value_block
29
+
8
30
  FALSES = [
9
31
  nil, "", false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"
10
32
  ].freeze
11
33
 
12
34
  def boolean_options_string
13
- boolean_options.map do |option|
35
+ boolean_options.filter_map do |option|
14
36
  option.to_s.upcase.split("_").join unless
15
37
  FALSES.include?(send(option))
16
- end.compact
38
+ end
17
39
  end
18
40
  end
19
41
  end
@@ -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
@@ -20,7 +19,7 @@ module RediSearch
20
19
 
21
20
  private
22
21
 
23
- attr_reader :name, :sortable, :no_index
22
+ attr_reader :sortable, :no_index
24
23
 
25
24
  def boolean_options
26
25
  %i(sortable no_index)
@@ -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
@@ -20,7 +19,7 @@ module RediSearch
20
19
 
21
20
  private
22
21
 
23
- attr_reader :name, :sortable, :no_index
22
+ attr_reader :sortable, :no_index
24
23
 
25
24
  def boolean_options
26
25
  %i(sortable no_index)
@@ -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,9 +20,17 @@ module RediSearch
20
20
  query
21
21
  end
22
22
 
23
+ def coerce(value)
24
+ value.join(separator)
25
+ end
26
+
27
+ def tag?
28
+ true
29
+ end
30
+
23
31
  private
24
32
 
25
- attr_reader :name, :separator, :sortable, :no_index
33
+ attr_reader :separator, :sortable, :no_index
26
34
 
27
35
  def boolean_options
28
36
  %i(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
@@ -24,7 +25,7 @@ module RediSearch
24
25
 
25
26
  private
26
27
 
27
- attr_reader :name, :weight, :phonetic, :sortable, :no_index, :no_stem
28
+ attr_reader :weight, :phonetic, :sortable, :no_index, :no_stem
28
29
 
29
30
  def boolean_options
30
31
  %i(sortable no_index no_stem)
@@ -1,41 +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).to_a
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
- raw.map do |field_name, options|
24
- self.class.make_field(field_name, options)
25
- end.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))
23
+ end
24
+
25
+ def geo_field(name, **options, &block)
26
+ self[name] || push(Schema::GeoField.new(name, **options, &block))
27
+ end
28
+
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)
26
36
  end
27
37
 
28
- def fields
29
- raw.keys
38
+ def to_a
39
+ fields.map(&:to_a).flatten
30
40
  end
31
41
 
32
- def add_field(field_name, options)
33
- raw[field_name] = options
34
- self
42
+ def [](name)
43
+ fields.find { |field| field.name == name }
35
44
  end
36
45
 
37
46
  private
38
47
 
39
- attr_accessor :raw
48
+ def push(field)
49
+ @fields.push(field)
50
+
51
+ field
52
+ end
40
53
  end
41
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
@@ -26,11 +26,11 @@ module RediSearch
26
26
  results
27
27
  end
28
28
 
29
- #:nocov:
29
+ # :nocov:
30
30
  def pretty_print(printer)
31
31
  printer.pp(results)
32
32
  end
33
- #:nocov:
33
+ # :nocov:
34
34
 
35
35
  private
36
36