redi_search 1.0.5 → 2.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -156
  3. data/.travis.yml +47 -15
  4. data/Appraisals +4 -8
  5. data/Gemfile +3 -4
  6. data/README.md +74 -73
  7. data/Rakefile +15 -3
  8. data/bin/console +36 -0
  9. data/gemfiles/{rails_6.gemfile → activerecord_51.gemfile} +1 -5
  10. data/gemfiles/{rails_51.gemfile → activerecord_52.gemfile} +1 -5
  11. data/lib/redi_search.rb +6 -7
  12. data/lib/redi_search/add.rb +9 -5
  13. data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
  14. data/lib/redi_search/client.rb +8 -6
  15. data/lib/redi_search/client/response.rb +4 -0
  16. data/lib/redi_search/configuration.rb +1 -11
  17. data/lib/redi_search/create.rb +6 -4
  18. data/lib/redi_search/document.rb +5 -4
  19. data/lib/redi_search/document/display.rb +9 -9
  20. data/lib/redi_search/document/finder.rb +10 -2
  21. data/lib/redi_search/index.rb +9 -9
  22. data/lib/redi_search/lazily_load.rb +6 -11
  23. data/lib/redi_search/log_subscriber.rb +23 -52
  24. data/lib/redi_search/model.rb +43 -39
  25. data/lib/redi_search/schema.rb +3 -3
  26. data/lib/redi_search/schema/tag_field.rb +1 -1
  27. data/lib/redi_search/schema/text_field.rb +1 -1
  28. data/lib/redi_search/search.rb +12 -25
  29. data/lib/redi_search/search/clauses.rb +6 -7
  30. data/lib/redi_search/search/clauses/application_clause.rb +20 -5
  31. data/lib/redi_search/search/clauses/boolean.rb +5 -5
  32. data/lib/redi_search/search/clauses/highlight.rb +18 -2
  33. data/lib/redi_search/search/clauses/limit.rb +7 -5
  34. data/lib/redi_search/search/clauses/return.rb +1 -1
  35. data/lib/redi_search/search/clauses/slop.rb +1 -1
  36. data/lib/redi_search/search/clauses/sort_by.rb +1 -1
  37. data/lib/redi_search/search/clauses/where.rb +10 -2
  38. data/lib/redi_search/search/result.rb +9 -9
  39. data/lib/redi_search/search/term.rb +7 -6
  40. data/lib/redi_search/spellcheck.rb +3 -4
  41. data/lib/redi_search/spellcheck/result.rb +1 -1
  42. data/lib/redi_search/validatable.rb +49 -0
  43. data/lib/redi_search/validations/inclusion.rb +26 -0
  44. data/lib/redi_search/validations/numericality.rb +45 -0
  45. data/lib/redi_search/validations/presence.rb +29 -0
  46. data/lib/redi_search/version.rb +1 -1
  47. data/redi_search.gemspec +0 -2
  48. metadata +9 -41
  49. data/bin/test +0 -7
  50. data/gemfiles/rails_52.gemfile +0 -17
data/Rakefile CHANGED
@@ -1,12 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
4
5
  require "rake/testtask"
5
6
 
6
- Rake::TestTask.new(:test) do |t|
7
+ RuboCop::RakeTask.new(:rubocop) do |t|
8
+ t.options = ["--display-cop-names"]
9
+ end
10
+
11
+ Rake::TestTask.new("test:unit") do |t|
12
+ t.libs << "test"
13
+ t.libs << "lib"
14
+ t.test_files = FileList["test/unit/**/*_test.rb"]
15
+ end
16
+
17
+ Rake::TestTask.new("test:integration") do |t|
7
18
  t.libs << "test"
8
19
  t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
20
+ t.test_files = FileList["test/integration/**/*_test.rb"]
10
21
  end
11
22
 
12
- task default: :test
23
+ task test: [:default]
24
+ task default: ["test:integration", "test:unit", :rubocop]
data/bin/console CHANGED
@@ -4,5 +4,41 @@
4
4
  require "bundler/setup"
5
5
  require "redi_search"
6
6
 
7
+ require "faker"
7
8
  require "pry"
9
+ require "active_support/logger"
10
+ require "active_record"
11
+
12
+ ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT)
13
+
14
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
15
+
16
+ ActiveRecord::Migration.create_table :users do |t|
17
+ t.string :first
18
+ t.string :last
19
+ end
20
+
21
+ class User < ActiveRecord::Base
22
+ redi_search schema: {
23
+ first: { text: { phonetic: "dm:en" } },
24
+ last: { text: { phonetic: "dm:en" } }
25
+ }
26
+ end
27
+
28
+ def seed_users(count = 10_000)
29
+ User.insert_all(
30
+ Array.new(count).map do
31
+ { first: Faker::Name.first_name, last: Faker::Name.last_name }
32
+ end
33
+ )
34
+ User.reindex
35
+ end
36
+
37
+ def reload!
38
+ Object.send :remove_const, :RediSearch
39
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/redi_search\// }
40
+ files.each { |file| load file }
41
+ true
42
+ end
43
+
8
44
  Pry.start
@@ -2,16 +2,12 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "faker"
6
- gem "minitest", "~> 5.0"
7
5
  gem "mocha"
8
6
  gem "pry"
9
- gem "pry-rails"
10
7
  gem "rubocop"
11
8
  gem "rubocop-performance"
12
- gem "rubocop-rails"
13
9
  gem "simplecov"
14
10
  gem "sqlite3"
15
- gem "rails", "6.0.0.rc1"
11
+ gem "activerecord", "< 5.2", ">= 5.1"
16
12
 
17
13
  gemspec path: "../"
@@ -2,16 +2,12 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "faker"
6
- gem "minitest", "~> 5.0"
7
5
  gem "mocha"
8
6
  gem "pry"
9
- gem "pry-rails"
10
7
  gem "rubocop"
11
8
  gem "rubocop-performance"
12
- gem "rubocop-rails"
13
9
  gem "simplecov"
14
10
  gem "sqlite3"
15
- gem "rails", "< 5.2", ">= 5.1"
11
+ gem "activerecord", "< 6.0", ">= 5.2"
16
12
 
17
13
  gemspec path: "../"
data/lib/redi_search.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "redis"
4
- require "active_support"
5
- require "active_model"
6
- require "active_support/core_ext/object/blank"
4
+ require "active_support/lazy_load_hooks"
7
5
 
8
6
  require "redi_search/configuration"
7
+ require "redi_search/client"
9
8
 
10
9
  require "redi_search/model"
11
10
  require "redi_search/index"
@@ -14,8 +13,6 @@ require "redi_search/document"
14
13
 
15
14
  module RediSearch
16
15
  class << self
17
- extend Forwardable
18
-
19
16
  attr_writer :configuration
20
17
 
21
18
  def configuration
@@ -30,10 +27,12 @@ module RediSearch
30
27
  yield(configuration)
31
28
  end
32
29
 
33
- def_delegator :configuration, :client
30
+ def client
31
+ @client ||= Client.new(Redis.new(configuration.redis_config.to_h))
32
+ end
34
33
 
35
34
  def env
36
- @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
35
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
37
36
  end
38
37
  end
39
38
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "redi_search/validatable"
4
+
3
5
  module RediSearch
4
6
  class Add
5
- include ActiveModel::Validations
7
+ include Validatable
6
8
 
7
- validates :score, numericality: {
8
- greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0
9
- }
9
+ validates_numericality_of :score, within: 0.0..1.0
10
10
 
11
11
  def initialize(index, document, score: 1.0, replace: {}, language: nil,
12
12
  no_save: false)
@@ -48,7 +48,11 @@ module RediSearch
48
48
  end
49
49
 
50
50
  def replace?
51
- replace.present?
51
+ if replace.respond_to?(:empty?)
52
+ !replace.empty?
53
+ else
54
+ replace
55
+ end
52
56
  end
53
57
 
54
58
  def replace_options
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RediSearch
4
- class Alter
4
+ class AddField
5
5
  def initialize(index, field_name, schema)
6
6
  @index = index
7
7
  @field_name = field_name
@@ -9,17 +9,25 @@ module RediSearch
9
9
  end
10
10
 
11
11
  def call!
12
- index.schema.alter(field_name, raw_schema)
12
+ index.schema.add_field(field_name, raw_schema)
13
13
 
14
- RediSearch.client.call!(
15
- "ALTER", index.name, "SCHEMA", "ADD", *field_schema
16
- ).ok?
14
+ RediSearch.client.call!(*command).ok?
15
+ end
16
+
17
+ def call
18
+ call!
19
+ rescue Redis::CommandError
20
+ false
17
21
  end
18
22
 
19
23
  private
20
24
 
21
25
  attr_reader :index, :field_name, :raw_schema
22
26
 
27
+ def command
28
+ ["ALTER", index.name, "SCHEMA", "ADD", *field_schema]
29
+ end
30
+
23
31
  def field_schema
24
32
  @field_schema ||= Schema.make_field(field_name, raw_schema)
25
33
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "redis"
4
+ require "active_support/notifications"
5
+
4
6
  require "redi_search/client/response"
5
7
 
6
8
  module RediSearch
7
9
  class Client
8
- def initialize(redis_config)
9
- @redis = Redis.new(redis_config)
10
+ def initialize(redis = Redis.new)
11
+ @redis = redis
10
12
  end
11
13
 
12
14
  def call!(command, *params)
@@ -15,8 +17,8 @@ module RediSearch
15
17
  end
16
18
  end
17
19
 
18
- def pipelined
19
- Response.new(redis.pipelined do
20
+ def multi
21
+ Response.new(redis.multi do
20
22
  instrument("pipeline", query: ["begin pipeline"])
21
23
  yield
22
24
  instrument("pipeline", query: ["finish pipeline"])
@@ -33,8 +35,8 @@ module RediSearch
33
35
 
34
36
  def instrument(action, payload, &block)
35
37
  ActiveSupport::Notifications.instrument(
36
- "#{action}.redi_search",
37
- { name: "RediSearch" }.merge(payload),
38
+ "action.redi_search",
39
+ { name: "RediSearch", action: action }.merge(payload),
38
40
  &Proc.new(&(block || proc {}))
39
41
  )
40
42
  end
@@ -12,6 +12,10 @@ module RediSearch
12
12
  end
13
13
  end
14
14
 
15
+ def nil?
16
+ response.nil?
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def array_ok?
@@ -1,17 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/client"
4
-
5
3
  module RediSearch
6
4
  class Configuration
7
- attr_writer :redis_config
8
-
9
- def client
10
- @client ||= Client.new(redis_config)
11
- end
12
-
13
- def redis_config
14
- @redis_config ||= { host: "127.0.0.1", port: "6379" }
15
- end
5
+ attr_accessor :redis_config
16
6
  end
17
7
  end
@@ -17,9 +17,7 @@ module RediSearch
17
17
  end
18
18
 
19
19
  def call!
20
- RediSearch.client.call!(
21
- "CREATE", index.name, *extract_options.compact, "SCHEMA", schema.to_a
22
- ).ok?
20
+ RediSearch.client.call!(*command).ok?
23
21
  end
24
22
 
25
23
  def call
@@ -32,12 +30,16 @@ module RediSearch
32
30
 
33
31
  attr_reader :index, :schema, :options
34
32
 
33
+ def command
34
+ ["CREATE", index.name, *extract_options.compact, "SCHEMA", schema.to_a]
35
+ end
36
+
35
37
  def extract_options
36
38
  options.map do |clause, switch|
37
39
  next unless OPTION_MAPPER.key?(clause.to_sym) && switch
38
40
 
39
41
  OPTION_MAPPER[clause.to_sym]
40
- end << temporary_option
42
+ end + temporary_option
41
43
  end
42
44
 
43
45
  def temporary_option
@@ -12,7 +12,7 @@ module RediSearch
12
12
  object_to_serialize = serializer&.new(record) || record
13
13
 
14
14
  field_values = index.schema.fields.map do |field|
15
- next if only.present? && !only.include?(field.to_sym)
15
+ next unless only.empty? || only.include?(field.to_sym)
16
16
 
17
17
  [field.to_s, object_to_serialize.public_send(field)]
18
18
  end.compact.to_h
@@ -41,7 +41,8 @@ module RediSearch
41
41
  end
42
42
 
43
43
  def del(delete_document: false)
44
- call!("DEL", index.name, document_id, ("DD" if delete_document)).ok?
44
+ command = ["DEL", index.name, document_id, ("DD" if delete_document)]
45
+ call!(*command.compact).ok?
45
46
  end
46
47
 
47
48
  def schema_fields
@@ -61,7 +62,7 @@ module RediSearch
61
62
  end
62
63
 
63
64
  def document_id_without_index
64
- if @document_id.to_s.starts_with? index.name
65
+ if @document_id.to_s.start_with? index.name
65
66
  @document_id.gsub(index.name, "")
66
67
  else
67
68
  @document_id
@@ -78,7 +79,7 @@ module RediSearch
78
79
 
79
80
  def load_attributes
80
81
  attributes.each do |field, value|
81
- next unless schema_fields.include? field
82
+ next unless schema_fields.include? field.to_s
82
83
 
83
84
  instance_variable_set(:"@#{field}", value)
84
85
  define_singleton_method(field) { value }
@@ -3,7 +3,6 @@
3
3
  module RediSearch
4
4
  class Document
5
5
  module Display
6
- #:nocov:
7
6
  def inspect
8
7
  inspection = pretty_print_attributes.map do |field_name|
9
8
  "#{field_name}: #{public_send(field_name)}"
@@ -12,6 +11,15 @@ module RediSearch
12
11
  "#<#{self.class} #{inspection}>"
13
12
  end
14
13
 
14
+ def pretty_print_attributes
15
+ pp_attrs = attributes.keys.dup
16
+ pp_attrs.push("document_id")
17
+ pp_attrs.push("score") if score
18
+
19
+ pp_attrs.compact
20
+ end
21
+
22
+ #:nocov:
15
23
  def pretty_print(printer) # rubocop:disable Metrics/MethodLength
16
24
  printer.object_address_group(self) do
17
25
  printer.seplist(
@@ -27,14 +35,6 @@ module RediSearch
27
35
  end
28
36
  end
29
37
  end
30
-
31
- def pretty_print_attributes
32
- pp_attrs = attributes.keys.dup
33
- pp_attrs.push("document_id")
34
- pp_attrs.push("score") if score.present?
35
-
36
- pp_attrs.compact
37
- end
38
38
  #:nocov:
39
39
  end
40
40
  end
@@ -5,7 +5,7 @@ module RediSearch
5
5
  class Finder
6
6
  def initialize(index, *document_ids)
7
7
  @index = index
8
- @document_ids = Array.wrap(document_ids)
8
+ @document_ids = [*document_ids]
9
9
  end
10
10
 
11
11
  def find
@@ -61,10 +61,18 @@ module RediSearch
61
61
  end
62
62
 
63
63
  def parse_document(document_id, document_response)
64
- return if document_response.blank?
64
+ return unless document_response?(document_response)
65
65
 
66
66
  Document.new(index, document_id, Hash[*document_response])
67
67
  end
68
+
69
+ def document_response?(document_response)
70
+ if document_response.respond_to?(:empty?)
71
+ !document_response.empty?
72
+ else
73
+ !document_response.nil?
74
+ end
75
+ end
68
76
  end
69
77
  end
70
78
  end
@@ -5,14 +5,14 @@ require "redi_search/create"
5
5
  require "redi_search/schema"
6
6
  require "redi_search/search"
7
7
  require "redi_search/spellcheck"
8
- require "redi_search/alter"
8
+ require "redi_search/add_field"
9
9
 
10
10
  module RediSearch
11
11
  class Index
12
12
  attr_reader :name, :schema, :model
13
13
 
14
14
  def initialize(name, schema, model = nil)
15
- @name = name
15
+ @name = name.to_s
16
16
  @schema = Schema.new(schema)
17
17
  @model = model
18
18
  end
@@ -51,10 +51,10 @@ module RediSearch
51
51
  Add.new(self, document, **options).call!
52
52
  end
53
53
 
54
- def add_multiple!(documents, **options)
55
- client.pipelined do
54
+ def add_multiple(documents, **options)
55
+ client.multi do
56
56
  documents.each do |document|
57
- add!(document, **options)
57
+ add(document, **options)
58
58
  end
59
59
  end.ok?
60
60
  end
@@ -85,15 +85,15 @@ module RediSearch
85
85
  drop if recreate
86
86
  create unless exist?
87
87
 
88
- add_multiple! documents, **options
88
+ add_multiple documents, **options
89
89
  end
90
90
 
91
91
  def document_count
92
- info["num_docs"].to_i
92
+ info.num_docs.to_i
93
93
  end
94
94
 
95
- def alter(field_name, schema)
96
- Alter.new(self, field_name, schema).call!
95
+ def add_field(field_name, schema)
96
+ AddField.new(self, field_name, schema).call!
97
97
  end
98
98
 
99
99
  private