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