redi_search 0.1.0 → 1.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +516 -112
  3. data/lib/redi_search.rb +5 -2
  4. data/lib/redi_search/add.rb +70 -0
  5. data/lib/redi_search/alter.rb +30 -0
  6. data/lib/redi_search/create.rb +53 -0
  7. data/lib/redi_search/document.rb +71 -16
  8. data/lib/redi_search/index.rb +31 -26
  9. data/lib/redi_search/lazily_load.rb +65 -0
  10. data/lib/redi_search/log_subscriber.rb +4 -0
  11. data/lib/redi_search/model.rb +41 -18
  12. data/lib/redi_search/schema.rb +17 -8
  13. data/lib/redi_search/schema/text_field.rb +0 -2
  14. data/lib/redi_search/search.rb +22 -44
  15. data/lib/redi_search/search/clauses.rb +60 -31
  16. data/lib/redi_search/search/clauses/and.rb +17 -0
  17. data/lib/redi_search/search/clauses/application_clause.rb +18 -0
  18. data/lib/redi_search/search/clauses/boolean.rb +72 -0
  19. data/lib/redi_search/search/clauses/highlight.rb +47 -0
  20. data/lib/redi_search/search/clauses/in_order.rb +17 -0
  21. data/lib/redi_search/search/clauses/language.rb +23 -0
  22. data/lib/redi_search/search/clauses/limit.rb +27 -0
  23. data/lib/redi_search/search/clauses/no_content.rb +17 -0
  24. data/lib/redi_search/search/clauses/no_stop_words.rb +17 -0
  25. data/lib/redi_search/search/clauses/or.rb +23 -0
  26. data/lib/redi_search/search/clauses/return.rb +23 -0
  27. data/lib/redi_search/search/clauses/slop.rb +23 -0
  28. data/lib/redi_search/search/clauses/sort_by.rb +25 -0
  29. data/lib/redi_search/search/clauses/verbatim.rb +17 -0
  30. data/lib/redi_search/search/clauses/where.rb +66 -0
  31. data/lib/redi_search/search/clauses/with_scores.rb +17 -0
  32. data/lib/redi_search/search/result.rb +46 -0
  33. data/lib/redi_search/search/term.rb +4 -4
  34. data/lib/redi_search/spellcheck.rb +30 -29
  35. data/lib/redi_search/spellcheck/result.rb +44 -0
  36. data/lib/redi_search/version.rb +1 -1
  37. metadata +101 -31
  38. data/.gitignore +0 -11
  39. data/.rubocop.yml +0 -1757
  40. data/.travis.yml +0 -23
  41. data/Gemfile +0 -17
  42. data/Rakefile +0 -12
  43. data/bin/console +0 -8
  44. data/bin/publish +0 -58
  45. data/bin/setup +0 -8
  46. data/bin/test +0 -7
  47. data/lib/redi_search/document/converter.rb +0 -26
  48. data/lib/redi_search/error.rb +0 -6
  49. data/lib/redi_search/result/collection.rb +0 -22
  50. data/lib/redi_search/search/and_clause.rb +0 -15
  51. data/lib/redi_search/search/boolean_clause.rb +0 -72
  52. data/lib/redi_search/search/highlight_clause.rb +0 -43
  53. data/lib/redi_search/search/or_clause.rb +0 -21
  54. data/lib/redi_search/search/where_clause.rb +0 -66
  55. data/redi_search.gemspec +0 -48
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ class Search
5
+ module Clauses
6
+ class Boolean
7
+ def initialize(search, term, prior_clause = nil, **term_options)
8
+ @search = search
9
+ @prior_clause = prior_clause
10
+ @not = false
11
+
12
+ initialize_term(term, **term_options)
13
+ end
14
+
15
+ def to_s
16
+ raise ArgumentError, "missing query terms" if term.blank?
17
+
18
+ [
19
+ prior_clause.presence,
20
+ queryify_term.dup.prepend(not_operator)
21
+ ].compact.join(operand)
22
+ end
23
+
24
+ delegate :inspect, to: :to_s
25
+
26
+ def not(term, **term_options)
27
+ @not = true
28
+
29
+ initialize_term(term, **term_options)
30
+
31
+ search
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :prior_clause, :term, :search
37
+
38
+ def operand
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def not_operator
43
+ return "" unless @not
44
+
45
+ "-"
46
+ end
47
+
48
+ def initialize_term(term, **term_options)
49
+ return if term.blank?
50
+
51
+ @term =
52
+ if term.is_a? RediSearch::Search
53
+ term
54
+ else
55
+ Term.new(term, term_options)
56
+ end
57
+ end
58
+
59
+ def queryify_term
60
+ if term.is_a?(RediSearch::Search) &&
61
+ !term.term_clause.is_a?(RediSearch::Search::Clauses::Where)
62
+ "(#{term.term_clause})"
63
+ elsif term.is_a?(RediSearch::Search)
64
+ term.term_clause
65
+ else
66
+ term
67
+ end.to_s
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ class Search
5
+ module Clauses
6
+ class Highlight
7
+ def initialize(fields: [], opening_tag: "<b>", closing_tag: "</b>")
8
+ @fields = fields
9
+ @opening_tag = opening_tag
10
+ @closing_tag = closing_tag
11
+ end
12
+
13
+ def clause
14
+ [
15
+ "HIGHLIGHT",
16
+ fields_clause,
17
+ tags_clause,
18
+ ].compact.flatten(1)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :fields, :opening_tag, :closing_tag
24
+
25
+ def tags_clause
26
+ return if opening_tag.blank? && closing_tag.blank?
27
+
28
+ if opening_tag.present? && closing_tag.present?
29
+ ["TAGS", opening_tag, closing_tag]
30
+ else
31
+ arg_error("Missing opening or closing tag")
32
+ end
33
+ end
34
+
35
+ def fields_clause
36
+ return if fields.empty?
37
+
38
+ ["FIELDS", fields.size, fields]
39
+ end
40
+
41
+ def arg_error(msg)
42
+ raise ArgumentError, "Highlight: #{msg}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class InOrder < ApplicationClause
9
+ def clause
10
+ validate!
11
+
12
+ ["INORDER"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Language < ApplicationClause
9
+ clause_term :language, presence: true
10
+
11
+ def initialize(language:)
12
+ @language = language
13
+ end
14
+
15
+ def clause
16
+ validate!
17
+
18
+ ["LANGUAGE", language]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Limit < ApplicationClause
9
+ clause_term :total, presence: true,
10
+ numericality: { greater_than_or_equal_to: 0 }
11
+ clause_term :offset, presence: true,
12
+ numericality: { greater_than_or_equal_to: 0 }
13
+
14
+ def initialize(total:, offset: 0)
15
+ @total = total
16
+ @offset = offset
17
+ end
18
+
19
+ def clause
20
+ validate!
21
+
22
+ ["LIMIT", [offset, total]]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class NoContent < ApplicationClause
9
+ def clause
10
+ validate!
11
+
12
+ ["NOCONTENT"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class NoStopWords < ApplicationClause
9
+ def clause
10
+ validate!
11
+
12
+ ["NOSTOPWORDS"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/boolean"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Or < Boolean
9
+ def where(**condition)
10
+ @term = search.dup.where(condition)
11
+
12
+ search
13
+ end
14
+
15
+ private
16
+
17
+ def operand
18
+ "|"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Return < ApplicationClause
9
+ clause_term :fields, presence: true
10
+
11
+ def initialize(fields:)
12
+ @fields = fields
13
+ end
14
+
15
+ def clause
16
+ validate!
17
+
18
+ ["RETURN", fields.size, fields]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Slop < ApplicationClause
9
+ clause_term :slop, numericality: { greater_than_or_equal_to: 0 }
10
+
11
+ def initialize(slop:)
12
+ @slop = slop
13
+ end
14
+
15
+ def clause
16
+ validate!
17
+
18
+ ["SLOP", slop]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class SortBy < ApplicationClause
9
+ clause_term :field, presence: true
10
+ clause_term :order, presence: true, inclusion: { in: %i(asc desc) }
11
+
12
+ def initialize(field:, order: :asc)
13
+ @field = field
14
+ @order = order.to_sym
15
+ end
16
+
17
+ def clause
18
+ validate!
19
+
20
+ ["SORTBY", field, order]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class Verbatim < ApplicationClause
9
+ def clause
10
+ validate!
11
+
12
+ ["VERBATIM"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ class Search
5
+ module Clauses
6
+ class Where
7
+ def initialize(search, condition, prior_clause = nil)
8
+ @search = search
9
+ @prior_clause = prior_clause
10
+ @not = false
11
+
12
+ initialize_term(condition)
13
+ end
14
+
15
+ def to_s
16
+ [
17
+ prior_clause.presence,
18
+ "(#{not_operator}@#{field}:#{queryify_term})"
19
+ ].compact.join(" ")
20
+ end
21
+
22
+ delegate :inspect, to: :to_s
23
+
24
+ def not(condition)
25
+ @not = true
26
+
27
+ initialize_term(condition)
28
+
29
+ search
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :prior_clause, :term, :field, :search
35
+
36
+ def queryify_term
37
+ if term.is_a? RediSearch::Search
38
+ "(#{term.term_clause})"
39
+ else
40
+ term.to_s
41
+ end
42
+ end
43
+
44
+ def not_operator
45
+ return "" unless @not
46
+
47
+ "-"
48
+ end
49
+
50
+ def initialize_term(condition)
51
+ return if condition.blank?
52
+
53
+ condition, *options = condition.to_a
54
+
55
+ @field = condition[0]
56
+ @term =
57
+ if condition[1].is_a? RediSearch::Search
58
+ condition[1]
59
+ else
60
+ Term.new(condition[1], **options.to_h)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redi_search/search/clauses/application_clause"
4
+
5
+ module RediSearch
6
+ class Search
7
+ module Clauses
8
+ class WithScores < ApplicationClause
9
+ def clause
10
+ validate!
11
+
12
+ ["WITHSCORES"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ class Search
5
+ class Result < Array
6
+ def initialize(index, used_clauses, count, documents)
7
+ @count = count
8
+ @used_clauses = used_clauses
9
+
10
+ super(documents.each_slice(response_slice).map do |slice|
11
+ document_id = slice[0]
12
+ fields = slice.last unless no_content?
13
+ score = slice[1].to_f if with_scores?
14
+
15
+ Document.new(index, document_id, Hash[*fields.to_a], score)
16
+ end)
17
+ end
18
+
19
+ def count
20
+ @count || super
21
+ end
22
+
23
+ def size
24
+ @count || super
25
+ end
26
+
27
+ private
28
+
29
+ def response_slice
30
+ slice_length = 2
31
+ slice_length -= 1 if no_content?
32
+ slice_length += 1 if with_scores?
33
+
34
+ slice_length
35
+ end
36
+
37
+ def with_scores?
38
+ @used_clauses.include? "with_scores"
39
+ end
40
+
41
+ def no_content?
42
+ @used_clauses.include? "no_content"
43
+ end
44
+ end
45
+ end
46
+ end