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.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -156
- data/.travis.yml +47 -15
- data/Appraisals +4 -8
- data/Gemfile +3 -4
- data/README.md +74 -73
- data/Rakefile +15 -3
- data/bin/console +36 -0
- data/gemfiles/{rails_6.gemfile → activerecord_51.gemfile} +1 -5
- data/gemfiles/{rails_51.gemfile → activerecord_52.gemfile} +1 -5
- data/lib/redi_search.rb +6 -7
- data/lib/redi_search/add.rb +9 -5
- data/lib/redi_search/{alter.rb → add_field.rb} +13 -5
- data/lib/redi_search/client.rb +8 -6
- data/lib/redi_search/client/response.rb +4 -0
- data/lib/redi_search/configuration.rb +1 -11
- data/lib/redi_search/create.rb +6 -4
- data/lib/redi_search/document.rb +5 -4
- data/lib/redi_search/document/display.rb +9 -9
- data/lib/redi_search/document/finder.rb +10 -2
- data/lib/redi_search/index.rb +9 -9
- data/lib/redi_search/lazily_load.rb +6 -11
- data/lib/redi_search/log_subscriber.rb +23 -52
- data/lib/redi_search/model.rb +43 -39
- data/lib/redi_search/schema.rb +3 -3
- data/lib/redi_search/schema/tag_field.rb +1 -1
- data/lib/redi_search/schema/text_field.rb +1 -1
- data/lib/redi_search/search.rb +12 -25
- data/lib/redi_search/search/clauses.rb +6 -7
- data/lib/redi_search/search/clauses/application_clause.rb +20 -5
- data/lib/redi_search/search/clauses/boolean.rb +5 -5
- data/lib/redi_search/search/clauses/highlight.rb +18 -2
- data/lib/redi_search/search/clauses/limit.rb +7 -5
- data/lib/redi_search/search/clauses/return.rb +1 -1
- data/lib/redi_search/search/clauses/slop.rb +1 -1
- data/lib/redi_search/search/clauses/sort_by.rb +1 -1
- data/lib/redi_search/search/clauses/where.rb +10 -2
- data/lib/redi_search/search/result.rb +9 -9
- data/lib/redi_search/search/term.rb +7 -6
- data/lib/redi_search/spellcheck.rb +3 -4
- data/lib/redi_search/spellcheck/result.rb +1 -1
- data/lib/redi_search/validatable.rb +49 -0
- data/lib/redi_search/validations/inclusion.rb +26 -0
- data/lib/redi_search/validations/numericality.rb +45 -0
- data/lib/redi_search/validations/presence.rb +29 -0
- data/lib/redi_search/version.rb +1 -1
- data/redi_search.gemspec +0 -2
- metadata +9 -41
- data/bin/test +0 -7
- 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
|
-
|
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
|
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 "
|
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 "
|
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
|
-
|
30
|
+
def client
|
31
|
+
@client ||= Client.new(Redis.new(configuration.redis_config.to_h))
|
32
|
+
end
|
34
33
|
|
35
34
|
def env
|
36
|
-
|
35
|
+
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
data/lib/redi_search/add.rb
CHANGED
@@ -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
|
7
|
+
include Validatable
|
6
8
|
|
7
|
-
|
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.
|
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
|
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.
|
12
|
+
index.schema.add_field(field_name, raw_schema)
|
13
13
|
|
14
|
-
RediSearch.client.call!(
|
15
|
-
|
16
|
-
|
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
|
data/lib/redi_search/client.rb
CHANGED
@@ -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(
|
9
|
-
@redis =
|
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
|
19
|
-
Response.new(redis.
|
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
|
-
"
|
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
|
@@ -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
|
-
|
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
|
data/lib/redi_search/create.rb
CHANGED
@@ -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
|
42
|
+
end + temporary_option
|
41
43
|
end
|
42
44
|
|
43
45
|
def temporary_option
|
data/lib/redi_search/document.rb
CHANGED
@@ -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
|
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
|
-
|
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.
|
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 =
|
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
|
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
|
data/lib/redi_search/index.rb
CHANGED
@@ -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/
|
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
|
55
|
-
client.
|
54
|
+
def add_multiple(documents, **options)
|
55
|
+
client.multi do
|
56
56
|
documents.each do |document|
|
57
|
-
add
|
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
|
88
|
+
add_multiple documents, **options
|
89
89
|
end
|
90
90
|
|
91
91
|
def document_count
|
92
|
-
info
|
92
|
+
info.num_docs.to_i
|
93
93
|
end
|
94
94
|
|
95
|
-
def
|
96
|
-
|
95
|
+
def add_field(field_name, schema)
|
96
|
+
AddField.new(self, field_name, schema).call!
|
97
97
|
end
|
98
98
|
|
99
99
|
private
|