redi_search 0.1.0 → 1.0.0

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