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.
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: []