redi_search 1.0.5 → 2.0.0

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