redi_search 3.0.0 → 6.0.0

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