redi_search 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rubocop.yml +1757 -0
  4. data/.travis.yml +23 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +220 -0
  9. data/Rakefile +12 -0
  10. data/bin/console +8 -0
  11. data/bin/publish +58 -0
  12. data/bin/setup +8 -0
  13. data/bin/test +7 -0
  14. data/lib/redi_search/client.rb +68 -0
  15. data/lib/redi_search/configuration.rb +17 -0
  16. data/lib/redi_search/document/converter.rb +26 -0
  17. data/lib/redi_search/document.rb +79 -0
  18. data/lib/redi_search/error.rb +6 -0
  19. data/lib/redi_search/index.rb +100 -0
  20. data/lib/redi_search/log_subscriber.rb +94 -0
  21. data/lib/redi_search/model.rb +57 -0
  22. data/lib/redi_search/result/collection.rb +22 -0
  23. data/lib/redi_search/schema/field.rb +21 -0
  24. data/lib/redi_search/schema/geo_field.rb +30 -0
  25. data/lib/redi_search/schema/numeric_field.rb +30 -0
  26. data/lib/redi_search/schema/tag_field.rb +32 -0
  27. data/lib/redi_search/schema/text_field.rb +36 -0
  28. data/lib/redi_search/schema.rb +34 -0
  29. data/lib/redi_search/search/and_clause.rb +15 -0
  30. data/lib/redi_search/search/boolean_clause.rb +72 -0
  31. data/lib/redi_search/search/clauses.rb +89 -0
  32. data/lib/redi_search/search/highlight_clause.rb +43 -0
  33. data/lib/redi_search/search/or_clause.rb +21 -0
  34. data/lib/redi_search/search/term.rb +72 -0
  35. data/lib/redi_search/search/where_clause.rb +66 -0
  36. data/lib/redi_search/search.rb +89 -0
  37. data/lib/redi_search/spellcheck.rb +53 -0
  38. data/lib/redi_search/version.rb +5 -0
  39. data/lib/redi_search.rb +40 -0
  40. data/redi_search.gemspec +48 -0
  41. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ VERSION = "0.1.0"
5
+ end
@@ -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
@@ -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: []