redi_search 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +1757 -0
- data/.travis.yml +23 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/Rakefile +12 -0
- data/bin/console +8 -0
- data/bin/publish +58 -0
- data/bin/setup +8 -0
- data/bin/test +7 -0
- data/lib/redi_search/client.rb +68 -0
- data/lib/redi_search/configuration.rb +17 -0
- data/lib/redi_search/document/converter.rb +26 -0
- data/lib/redi_search/document.rb +79 -0
- data/lib/redi_search/error.rb +6 -0
- data/lib/redi_search/index.rb +100 -0
- data/lib/redi_search/log_subscriber.rb +94 -0
- data/lib/redi_search/model.rb +57 -0
- data/lib/redi_search/result/collection.rb +22 -0
- data/lib/redi_search/schema/field.rb +21 -0
- data/lib/redi_search/schema/geo_field.rb +30 -0
- data/lib/redi_search/schema/numeric_field.rb +30 -0
- data/lib/redi_search/schema/tag_field.rb +32 -0
- data/lib/redi_search/schema/text_field.rb +36 -0
- data/lib/redi_search/schema.rb +34 -0
- data/lib/redi_search/search/and_clause.rb +15 -0
- data/lib/redi_search/search/boolean_clause.rb +72 -0
- data/lib/redi_search/search/clauses.rb +89 -0
- data/lib/redi_search/search/highlight_clause.rb +43 -0
- data/lib/redi_search/search/or_clause.rb +21 -0
- data/lib/redi_search/search/term.rb +72 -0
- data/lib/redi_search/search/where_clause.rb +66 -0
- data/lib/redi_search/search.rb +89 -0
- data/lib/redi_search/spellcheck.rb +53 -0
- data/lib/redi_search/version.rb +5 -0
- data/lib/redi_search.rb +40 -0
- data/redi_search.gemspec +48 -0
- metadata +141 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
class Term
|
6
|
+
def initialize(term, **options)
|
7
|
+
@term = term
|
8
|
+
@options = options
|
9
|
+
|
10
|
+
validate_options
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
if @term.is_a? Range
|
15
|
+
stringify_range
|
16
|
+
else
|
17
|
+
stringify_query
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_accessor :term, :options
|
24
|
+
|
25
|
+
def fuzziness
|
26
|
+
@fuzziness ||= options[:fuzziness].to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def optional_operator
|
30
|
+
return unless options[:optional]
|
31
|
+
|
32
|
+
"~"
|
33
|
+
end
|
34
|
+
|
35
|
+
def prefix_operator
|
36
|
+
return unless options[:prefix]
|
37
|
+
|
38
|
+
"*"
|
39
|
+
end
|
40
|
+
|
41
|
+
def stringify_query
|
42
|
+
@term.to_s.
|
43
|
+
tr("`", "\`").
|
44
|
+
then { |str| "#{'%' * fuzziness}#{str}#{'%' * fuzziness}" }.
|
45
|
+
then { |str| "#{optional_operator}#{str}" }.
|
46
|
+
then { |str| "#{str}#{prefix_operator}" }.
|
47
|
+
then { |str| "`#{str}`" }
|
48
|
+
end
|
49
|
+
|
50
|
+
def stringify_range
|
51
|
+
first, last = @term.first, @term.last
|
52
|
+
first = "-inf" if first == -Float::INFINITY
|
53
|
+
last = "+inf" if last == Float::INFINITY
|
54
|
+
|
55
|
+
"[#{first} #{last}]"
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_options
|
59
|
+
unsupported_options =
|
60
|
+
(options.keys.map(&:to_s) - %w(fuzziness optional prefix)).join(", ")
|
61
|
+
|
62
|
+
if unsupported_options.present?
|
63
|
+
raise(ArgumentError,
|
64
|
+
"#{unsupported_options} are unsupported term options")
|
65
|
+
end
|
66
|
+
|
67
|
+
raise(ArgumentError, "fuzziness can only be between 0 and 3") if
|
68
|
+
fuzziness.negative? || fuzziness > 3
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Search
|
5
|
+
class WhereClause
|
6
|
+
def initialize(search, condition, prior_clause = nil)
|
7
|
+
@search = search
|
8
|
+
@prior_clause = prior_clause
|
9
|
+
@not = false
|
10
|
+
|
11
|
+
initialize_term(condition)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
[
|
16
|
+
prior_clause.presence,
|
17
|
+
"(#{not_operator}@#{field}:#{queryify_term})"
|
18
|
+
].compact.join(" ")
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
to_s.inspect
|
23
|
+
end
|
24
|
+
|
25
|
+
def not(condition)
|
26
|
+
@not = true
|
27
|
+
|
28
|
+
initialize_term(condition)
|
29
|
+
|
30
|
+
search
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :prior_clause, :term, :field, :search
|
36
|
+
|
37
|
+
def queryify_term
|
38
|
+
if term.is_a? RediSearch::Search
|
39
|
+
"(#{term.term_clause})"
|
40
|
+
else
|
41
|
+
term.to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def not_operator
|
46
|
+
return "" unless @not
|
47
|
+
|
48
|
+
"-"
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize_term(condition)
|
52
|
+
return if condition.blank?
|
53
|
+
|
54
|
+
condition, *options = condition.to_a
|
55
|
+
|
56
|
+
@field = condition[0]
|
57
|
+
@term =
|
58
|
+
if condition[1].is_a? RediSearch::Search
|
59
|
+
condition[1]
|
60
|
+
else
|
61
|
+
Term.new(condition[1], **options.to_h)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/search/clauses"
|
4
|
+
require "redi_search/search/term"
|
5
|
+
require "redi_search/search/highlight_clause"
|
6
|
+
require "redi_search/result/collection"
|
7
|
+
|
8
|
+
module RediSearch
|
9
|
+
class Search
|
10
|
+
include Enumerable
|
11
|
+
include Clauses
|
12
|
+
|
13
|
+
def initialize(index, term = nil, model = nil, **term_options)
|
14
|
+
@index = index
|
15
|
+
@model = model
|
16
|
+
@loaded = false
|
17
|
+
@no_content = false
|
18
|
+
@clauses = []
|
19
|
+
|
20
|
+
@term_clause = term.presence &&
|
21
|
+
AndClause.new(self, term, nil, **term_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
#:nocov:
|
25
|
+
def pretty_print(printer)
|
26
|
+
execute unless loaded?
|
27
|
+
|
28
|
+
printer.pp(records)
|
29
|
+
rescue Redis::CommandError => e
|
30
|
+
printer.pp(e.message)
|
31
|
+
end
|
32
|
+
#:nocov:
|
33
|
+
|
34
|
+
def loaded?
|
35
|
+
@loaded
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_a
|
39
|
+
execute unless loaded?
|
40
|
+
|
41
|
+
@records
|
42
|
+
end
|
43
|
+
|
44
|
+
def results
|
45
|
+
model.where(id: to_a.map(&:document_id))
|
46
|
+
end
|
47
|
+
|
48
|
+
delegate :count, :each, to: :to_a
|
49
|
+
|
50
|
+
def to_redis
|
51
|
+
command.map do |arg|
|
52
|
+
if !arg.to_s.starts_with?(/\(-?@/) && arg.to_s.split(/\s|\|/).size > 1
|
53
|
+
arg.inspect
|
54
|
+
else
|
55
|
+
arg
|
56
|
+
end
|
57
|
+
end.join(" ")
|
58
|
+
end
|
59
|
+
|
60
|
+
def dup
|
61
|
+
self.class.new(index)
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :term_clause
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :records
|
69
|
+
attr_accessor :index, :model, :clauses
|
70
|
+
|
71
|
+
def command
|
72
|
+
["SEARCH", index.name, term_clause, *clauses]
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute
|
76
|
+
@loaded = true
|
77
|
+
|
78
|
+
RediSearch.client.call!(*command).then do |results|
|
79
|
+
@records = Result::Collection.new(
|
80
|
+
index, results[0], results[1..-1].then do |docs|
|
81
|
+
next docs unless @no_content
|
82
|
+
|
83
|
+
docs.zip([[]] * results[0]).flatten(1)
|
84
|
+
end
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Spellcheck
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(index, query, distance: 1)
|
8
|
+
@index = index
|
9
|
+
@query = query
|
10
|
+
@distance = distance
|
11
|
+
@loaded = false
|
12
|
+
end
|
13
|
+
|
14
|
+
#:nocov:
|
15
|
+
def pretty_print(printer)
|
16
|
+
execute unless loaded?
|
17
|
+
|
18
|
+
printer.pp(records)
|
19
|
+
rescue Redis::CommandError => e
|
20
|
+
printer.pp(e.message)
|
21
|
+
end
|
22
|
+
#:nocov:
|
23
|
+
|
24
|
+
def loaded?
|
25
|
+
@loaded
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_a
|
29
|
+
execute unless loaded?
|
30
|
+
|
31
|
+
@records
|
32
|
+
end
|
33
|
+
|
34
|
+
delegate :count, :each, to: :to_a
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :records
|
39
|
+
attr_accessor :index, :query, :distance
|
40
|
+
|
41
|
+
def command
|
42
|
+
["SPELLCHECK", index.name, query, "DISTANCE", distance]
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute
|
46
|
+
@loaded = true
|
47
|
+
|
48
|
+
RediSearch.client.call!(*command).then do |results|
|
49
|
+
@records = results
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/redi_search.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
require "active_support"
|
5
|
+
require "active_support/core_ext/object"
|
6
|
+
require "active_support/core_ext/module/delegation"
|
7
|
+
|
8
|
+
require "redi_search/configuration"
|
9
|
+
|
10
|
+
require "redi_search/model"
|
11
|
+
require "redi_search/index"
|
12
|
+
require "redi_search/log_subscriber"
|
13
|
+
require "redi_search/document"
|
14
|
+
|
15
|
+
module RediSearch
|
16
|
+
class << self
|
17
|
+
attr_writer :configuration
|
18
|
+
|
19
|
+
def configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset
|
24
|
+
@configuration = Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure
|
28
|
+
yield(configuration)
|
29
|
+
end
|
30
|
+
|
31
|
+
def client
|
32
|
+
configuration.client
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
ActiveSupport.on_load(:active_record) do
|
38
|
+
include RediSearch::Model
|
39
|
+
end
|
40
|
+
RediSearch::LogSubscriber.attach_to :redi_search
|
data/redi_search.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "redi_search/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
8
|
+
spec.name = "redi_search"
|
9
|
+
spec.version = RediSearch::VERSION
|
10
|
+
spec.authors = ["Nick Pezza"]
|
11
|
+
spec.email = ["npezza93@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = %q(RediSearch ruby wrapper that can integrate with Rails)
|
14
|
+
spec.homepage = "https://github.com/npezza93/redi_search"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the
|
18
|
+
# 'allowed_push_host'
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to
|
20
|
+
# any host.
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
23
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
24
|
+
spec.metadata["changelog_uri"] =
|
25
|
+
"https://github.com/npezza93/redi_search/blob/master/changelog.md"
|
26
|
+
else
|
27
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
28
|
+
"public gem pushes."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Specify which files should be added to the gem when it is released.
|
32
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added
|
33
|
+
# into git.
|
34
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
35
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
36
|
+
f.match(%r{^(test|spec|features)/})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
spec.bindir = "exe"
|
40
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
41
|
+
spec.require_paths = ["lib"]
|
42
|
+
|
43
|
+
spec.add_runtime_dependency "activesupport"
|
44
|
+
spec.add_runtime_dependency "redis"
|
45
|
+
|
46
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
47
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redi_search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Pezza
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.17'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.17'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '12.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '12.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- npezza93@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rubocop.yml"
|
78
|
+
- ".travis.yml"
|
79
|
+
- CODE_OF_CONDUCT.md
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/publish
|
86
|
+
- bin/setup
|
87
|
+
- bin/test
|
88
|
+
- lib/redi_search.rb
|
89
|
+
- lib/redi_search/client.rb
|
90
|
+
- lib/redi_search/configuration.rb
|
91
|
+
- lib/redi_search/document.rb
|
92
|
+
- lib/redi_search/document/converter.rb
|
93
|
+
- lib/redi_search/error.rb
|
94
|
+
- lib/redi_search/index.rb
|
95
|
+
- lib/redi_search/log_subscriber.rb
|
96
|
+
- lib/redi_search/model.rb
|
97
|
+
- lib/redi_search/result/collection.rb
|
98
|
+
- lib/redi_search/schema.rb
|
99
|
+
- lib/redi_search/schema/field.rb
|
100
|
+
- lib/redi_search/schema/geo_field.rb
|
101
|
+
- lib/redi_search/schema/numeric_field.rb
|
102
|
+
- lib/redi_search/schema/tag_field.rb
|
103
|
+
- lib/redi_search/schema/text_field.rb
|
104
|
+
- lib/redi_search/search.rb
|
105
|
+
- lib/redi_search/search/and_clause.rb
|
106
|
+
- lib/redi_search/search/boolean_clause.rb
|
107
|
+
- lib/redi_search/search/clauses.rb
|
108
|
+
- lib/redi_search/search/highlight_clause.rb
|
109
|
+
- lib/redi_search/search/or_clause.rb
|
110
|
+
- lib/redi_search/search/term.rb
|
111
|
+
- lib/redi_search/search/where_clause.rb
|
112
|
+
- lib/redi_search/spellcheck.rb
|
113
|
+
- lib/redi_search/version.rb
|
114
|
+
- redi_search.gemspec
|
115
|
+
homepage: https://github.com/npezza93/redi_search
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata:
|
119
|
+
homepage_uri: https://github.com/npezza93/redi_search
|
120
|
+
source_code_uri: https://github.com/npezza93/redi_search
|
121
|
+
changelog_uri: https://github.com/npezza93/redi_search/blob/master/changelog.md
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubygems_version: 3.0.3
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: RediSearch ruby wrapper that can integrate with Rails
|
141
|
+
test_files: []
|