agnostic_backend 0.9.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 +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +52 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +345 -0
  12. data/Rakefile +6 -0
  13. data/agnostic_backend.gemspec +34 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +7 -0
  16. data/doc/indexable.md +292 -0
  17. data/doc/queryable.md +0 -0
  18. data/lib/agnostic_backend/cloudsearch/index.rb +99 -0
  19. data/lib/agnostic_backend/cloudsearch/index_field.rb +103 -0
  20. data/lib/agnostic_backend/cloudsearch/indexer.rb +84 -0
  21. data/lib/agnostic_backend/cloudsearch/remote_index_field.rb +32 -0
  22. data/lib/agnostic_backend/index.rb +24 -0
  23. data/lib/agnostic_backend/indexable/config.rb +24 -0
  24. data/lib/agnostic_backend/indexable/content_manager.rb +51 -0
  25. data/lib/agnostic_backend/indexable/field.rb +26 -0
  26. data/lib/agnostic_backend/indexable/field_type.rb +48 -0
  27. data/lib/agnostic_backend/indexable/indexable.rb +125 -0
  28. data/lib/agnostic_backend/indexer.rb +48 -0
  29. data/lib/agnostic_backend/queryable/attribute.rb +22 -0
  30. data/lib/agnostic_backend/queryable/cloudsearch/executor.rb +100 -0
  31. data/lib/agnostic_backend/queryable/cloudsearch/query.rb +29 -0
  32. data/lib/agnostic_backend/queryable/cloudsearch/query_builder.rb +13 -0
  33. data/lib/agnostic_backend/queryable/cloudsearch/result_set.rb +27 -0
  34. data/lib/agnostic_backend/queryable/cloudsearch/visitor.rb +127 -0
  35. data/lib/agnostic_backend/queryable/criteria/binary.rb +47 -0
  36. data/lib/agnostic_backend/queryable/criteria/criterion.rb +13 -0
  37. data/lib/agnostic_backend/queryable/criteria/ternary.rb +36 -0
  38. data/lib/agnostic_backend/queryable/criteria_builder.rb +83 -0
  39. data/lib/agnostic_backend/queryable/executor.rb +43 -0
  40. data/lib/agnostic_backend/queryable/expressions/expression.rb +65 -0
  41. data/lib/agnostic_backend/queryable/operations/n_ary.rb +18 -0
  42. data/lib/agnostic_backend/queryable/operations/operation.rb +13 -0
  43. data/lib/agnostic_backend/queryable/operations/unary.rb +35 -0
  44. data/lib/agnostic_backend/queryable/query.rb +27 -0
  45. data/lib/agnostic_backend/queryable/query_builder.rb +98 -0
  46. data/lib/agnostic_backend/queryable/result_set.rb +42 -0
  47. data/lib/agnostic_backend/queryable/tree_node.rb +48 -0
  48. data/lib/agnostic_backend/queryable/validator.rb +174 -0
  49. data/lib/agnostic_backend/queryable/value.rb +26 -0
  50. data/lib/agnostic_backend/queryable/visitor.rb +124 -0
  51. data/lib/agnostic_backend/rspec/matchers.rb +69 -0
  52. data/lib/agnostic_backend/utilities.rb +207 -0
  53. data/lib/agnostic_backend/version.rb +3 -0
  54. data/lib/agnostic_backend.rb +49 -0
  55. metadata +199 -0
@@ -0,0 +1,36 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ module Criteria
4
+ class Ternary < Criterion
5
+ attr_reader :attribute, :left_value, :right_value
6
+
7
+ def initialize(attribute:, left_value:, right_value:, context: nil)
8
+ @attribute, @left_value, @right_value = attribute, left_value, right_value
9
+ super([attribute, left_value, right_value], context)
10
+ end
11
+ end
12
+
13
+ class Between < Ternary
14
+ def initialize(attribute:, left_value:, right_value:, context: nil)
15
+ attribute = attribute_component(attribute: attribute, context: context)
16
+ left_value = value_component(value: left_value, context: context, type: attribute.type)
17
+ right_value = value_component(value: right_value, context: context, type: attribute.type)
18
+ super(attribute: attribute, left_value: left_value, right_value: right_value, context: context)
19
+ end
20
+ end
21
+
22
+ class GreaterAndLess < Between;
23
+ end
24
+
25
+ class GreaterEqualAndLess < Between;
26
+ end
27
+
28
+ class GreaterAndLessEqual < Between;
29
+ end
30
+
31
+ class GreaterEqualAndLessEqual < Between;
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class CriteriaBuilder
4
+
5
+ attr_reader :context
6
+
7
+ def initialize(query_builder)
8
+ @context = query_builder
9
+ end
10
+
11
+ def eq(attribute, value)
12
+ Criteria::Equal.new(attribute: attribute, value: value, context: context)
13
+ end
14
+
15
+ def neq(attribute, value)
16
+ Criteria::NotEqual.new(attribute: attribute, value: value, context: context)
17
+ end
18
+
19
+ def gt(attribute, value)
20
+ Criteria::Greater.new(attribute: attribute, value: value, context: context)
21
+ end
22
+
23
+ def lt(attribute, value)
24
+ Criteria::Less.new(attribute: attribute, value: value, context: context)
25
+ end
26
+
27
+ def ge(attribute, value)
28
+ Criteria::GreaterEqual.new(attribute: attribute, value: value, context: context)
29
+ end
30
+
31
+ def le(attribute, value)
32
+ Criteria::LessEqual.new(attribute: attribute, value: value, context: context)
33
+ end
34
+
35
+ def gt_and_lt(attribute, left_limit, right_limit)
36
+ Criteria::GreaterAndLess.new(attribute: attribute, left_value: left_limit, right_value: right_limit, context: context)
37
+ end
38
+
39
+ def gt_and_le(attribute, left_limit, right_limit)
40
+ Criteria::GreaterAndLessEqual.new(attribute: attribute, left_value: left_limit, right_value: right_limit, context: context)
41
+ end
42
+
43
+ def ge_and_lt(attribute, left_limit, right_limit)
44
+ Criteria::GreaterEqualAndLess.new(attribute: attribute, left_value: left_limit, right_value: right_limit, context: context)
45
+ end
46
+
47
+ def ge_and_le(attribute, left_limit, right_limit)
48
+ Criteria::GreaterEqualAndLessEqual.new(attribute: attribute, left_value: left_limit, right_value: right_limit, context: context)
49
+ end
50
+
51
+ def contains(attribute, value)
52
+ Criteria::Contains.new(attribute: attribute, value: value, context: context)
53
+ end
54
+
55
+ def starts(attribute, value)
56
+ Criteria::Starts.new(attribute: attribute, value: value, context: context)
57
+ end
58
+
59
+ def asc(attribute)
60
+ Operations::Ascending.new(attribute: attribute, context: context)
61
+ end
62
+
63
+ def desc(attribute)
64
+ Operations::Descending.new(attribute: attribute, context: context)
65
+ end
66
+
67
+ def not(criterion)
68
+ Operations::Not.new(operand: criterion, context: context)
69
+ end
70
+
71
+ def and(*criteria)
72
+ Operations::And.new(operands: criteria, context: context)
73
+ end
74
+
75
+ def or(*criteria)
76
+ Operations::Or.new(operands: criteria, context: context)
77
+ end
78
+
79
+ alias_method :all, :and
80
+ alias_method :any, :or
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,43 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class Executor
4
+
5
+ attr_reader :query
6
+ attr_reader :visitor
7
+
8
+ def initialize(query, visitor)
9
+ @query, @visitor = query, visitor
10
+ end
11
+
12
+ def execute
13
+ raise NotImplementedError, 'Abstract method'
14
+ end
15
+
16
+ private
17
+
18
+ def order_expression
19
+ query.children.find { |e| e.is_a? Expressions::Order }
20
+ end
21
+
22
+ def where_expression
23
+ query.children.find { |e| e.is_a? Expressions::Where }
24
+ end
25
+
26
+ def select_expression
27
+ query.children.find { |e| e.is_a? Expressions::Select }
28
+ end
29
+
30
+ def limit_expression
31
+ query.children.find { |e| e.is_a? Expressions::Limit }
32
+ end
33
+
34
+ def offset_expression
35
+ query.children.find { |e| e.is_a? Expressions::Offset }
36
+ end
37
+
38
+ def scroll_cursor_expression
39
+ query.children.find { |e| e.is_a? Expressions::ScrollCursor }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ module Expressions
4
+ class Expression < TreeNode
5
+ end
6
+
7
+ class Where < Expression
8
+ def initialize(criterion:, context:)
9
+ super([criterion], context)
10
+ end
11
+
12
+ def criterion
13
+ children.first
14
+ end
15
+ end
16
+
17
+ class Select < Expression
18
+ def initialize(attributes:, context:)
19
+ super(attributes.map { |a| Attribute.new(a, parent: self, context: context) }, context)
20
+ end
21
+
22
+ alias_method :projections, :children
23
+ end
24
+
25
+ class Order < Expression
26
+ def initialize(attributes:, context:)
27
+ super(attributes, context)
28
+ end
29
+
30
+ alias_method :qualifiers, :children
31
+ end
32
+
33
+ class Limit < Expression
34
+ def initialize(value:, context:)
35
+ super([Value.new(value, parent: self, context: context)], context)
36
+ end
37
+
38
+ def limit
39
+ children.first
40
+ end
41
+ end
42
+
43
+ class Offset < Expression
44
+ def initialize(value:, context:)
45
+ super([Value.new(value, parent: self, context: context)], context)
46
+ end
47
+
48
+ def offset
49
+ children.first
50
+ end
51
+ end
52
+
53
+ class ScrollCursor < Expression
54
+ def initialize(value:, context:)
55
+ super([Value.new(value, parent: self, context: context)], context)
56
+ end
57
+
58
+ def scroll_cursor
59
+ children.first
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ module Operations
4
+ class NAry < Operation
5
+
6
+ def initialize(operands:, context: nil)
7
+ super(operands, context)
8
+ end
9
+ end
10
+
11
+ class And < NAry;
12
+ end
13
+
14
+ class Or < NAry;
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ module Operations
4
+ class Operation < TreeNode
5
+ def initialize(operands = [], context = nil)
6
+ super
7
+ end
8
+
9
+ alias_method :operands, :children
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ module Operations
4
+ class Unary < Operation
5
+ attr_reader :operand
6
+
7
+ def initialize(operand:, context: nil)
8
+ @operand = operand
9
+ super([operand], context)
10
+ end
11
+ end
12
+
13
+ class Not < Unary
14
+ def initialize(operand:, context:)
15
+ super(operand: operand, context: context)
16
+ end
17
+ end
18
+
19
+ class OrderQualifier < Unary
20
+ def initialize(attribute:, context: nil)
21
+ attribute = attribute_component(attribute: attribute, context: context)
22
+ super(operand: attribute, context: context)
23
+ end
24
+
25
+ alias_method :attribute, :operand
26
+ end
27
+
28
+ class Ascending < OrderQualifier;
29
+ end
30
+
31
+ class Descending < OrderQualifier;
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class Query < TreeNode
4
+ attr_accessor :errors
5
+ attr_reader :context
6
+
7
+ def initialize(context)
8
+ super()
9
+ @errors ||= Hash.new { |hash, key| hash[key] = Array.new }
10
+ @context = context
11
+ end
12
+
13
+ def execute
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def valid?
18
+ self.accept(AgnosticBackend::Queryable::Validator.new)
19
+ end
20
+
21
+ def set_scroll_cursor(value)
22
+ context.scroll_cursor(value)
23
+ context.build
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class QueryBuilder
4
+
5
+ attr_reader :index
6
+ attr_reader :query
7
+
8
+ def initialize(index)
9
+ @index = index
10
+ end
11
+
12
+ def criteria_builder
13
+ CriteriaBuilder.new(self)
14
+ end
15
+
16
+ def where(criterion)
17
+ @criterion = criterion
18
+ self
19
+ end
20
+
21
+ def select(*attributes)
22
+ (@projections ||= []).push(*attributes)
23
+ self
24
+ end
25
+
26
+ def order(attribute, direction)
27
+ (@order_qualifiers ||= []).push build_order_qualifier(attribute, direction)
28
+ self
29
+ end
30
+
31
+ def limit(value)
32
+ @limit = value
33
+ self
34
+ end
35
+
36
+ def offset(value)
37
+ @offset = value
38
+ self
39
+ end
40
+
41
+ def scroll_cursor(value)
42
+ @scroll_cursor = value
43
+ self
44
+ end
45
+
46
+ def build
47
+ query = create_query(self)
48
+ query.children << build_where_expression if @criterion
49
+ query.children << build_select_expression if @projections
50
+ query.children << build_order_expression if @order_qualifiers
51
+ query.children << build_limit_expression if @limit
52
+ query.children << build_offset_expression if @offset
53
+ query.children << build_scroll_cursor_expression if @scroll_cursor
54
+
55
+ @query = query
56
+ end
57
+
58
+ private
59
+
60
+ def create_query(context)
61
+ raise NotImplementedError, 'AbstractMethod'
62
+ end
63
+
64
+ def build_where_expression
65
+ Expressions::Where.new(criterion: @criterion, context: self)
66
+ end
67
+
68
+ def build_select_expression
69
+ Expressions::Select.new(attributes: @projections, context: self)
70
+ end
71
+
72
+ def build_order_expression
73
+ Expressions::Order.new(attributes: @order_qualifiers, context: self)
74
+ end
75
+
76
+ def build_limit_expression
77
+ Expressions::Limit.new(value: @limit, context: self)
78
+ end
79
+
80
+ def build_offset_expression
81
+ Expressions::Offset.new(value: @offset, context: self)
82
+ end
83
+
84
+ def build_scroll_cursor_expression
85
+ Expressions::ScrollCursor.new(value: @scroll_cursor, context: self)
86
+ end
87
+
88
+ def build_order_qualifier(attribute, direction)
89
+ case direction
90
+ when :asc
91
+ Operations::Ascending.new(attribute: attribute, context: self)
92
+ when :desc
93
+ Operations::Descending.new(attribute: attribute, context: self)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class ResultSet
4
+
5
+ include Enumerable
6
+
7
+ attr_reader :raw_results, :query
8
+
9
+ def initialize(raw_results, query)
10
+ @raw_results, @query = raw_results, query
11
+ end
12
+
13
+ def each(&block)
14
+ filtered_results.each do |result|
15
+ block.call(transform(result))
16
+ end
17
+ end
18
+
19
+ def empty?
20
+ none?
21
+ end
22
+
23
+ def total_count
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def offset
28
+ raise NotImplementedError
29
+ end
30
+
31
+ private
32
+
33
+ def filtered_results
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def transform(result)
38
+ raise NotImplementedError
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class TreeNode
4
+ include AgnosticBackend::Utilities
5
+ # include Enumerable
6
+
7
+ attr_reader :children
8
+ attr_reader :context
9
+
10
+ def initialize(children = [], context = nil)
11
+ @children, @context = children, context
12
+ end
13
+
14
+ # def each(&block)
15
+ # block.call(self)
16
+ # children.each{|child| child.each(&block) }
17
+ # end
18
+
19
+ def ==(other)
20
+ return true if self.__id__ == other.__id__
21
+ return false if other.nil?
22
+ return false unless other.is_a? AgnosticBackend::Queryable::TreeNode
23
+
24
+ return false unless other.children.size == children.size
25
+ children_pairs = other.children.zip(children)
26
+
27
+ other.class == self.class &&
28
+ children_pairs.all? do |first_child, second_child|
29
+ first_child == second_child
30
+ end
31
+ end
32
+
33
+ def accept(visitor)
34
+ visitor.visit(self)
35
+ end
36
+
37
+ private
38
+
39
+ def attribute_component(attribute:, context: nil)
40
+ Attribute.new(attribute, parent: self, context: context)
41
+ end
42
+
43
+ def value_component(value:, context: nil, type:)
44
+ Value.new(convert_to(type, value), parent: self, context: context)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,174 @@
1
+ module AgnosticBackend
2
+ module Queryable
3
+ class Validator < AgnosticBackend::Queryable::Visitor
4
+
5
+ include AgnosticBackend::Utilities
6
+
7
+ def initialize
8
+ @valid = true
9
+ end
10
+
11
+ private
12
+
13
+ def visit_query(subject)
14
+ subject.children.each { |c| visit(c) }
15
+ @valid
16
+ end
17
+
18
+ def visit_criteria_equal(subject)
19
+ visit(subject.attribute)
20
+ visit(subject.value)
21
+ end
22
+
23
+ def visit_criteria_not_equal(subject)
24
+ visit(subject.attribute)
25
+ visit(subject.value)
26
+ end
27
+
28
+ def visit_criteria_greater(subject)
29
+ visit(subject.attribute)
30
+ visit(subject.value)
31
+ end
32
+
33
+ def visit_criteria_less(subject)
34
+ visit(subject.attribute)
35
+ visit(subject.value)
36
+ end
37
+
38
+ def visit_criteria_greater_equal(subject)
39
+ visit(subject.attribute)
40
+ visit(subject.value)
41
+ end
42
+
43
+ def visit_criteria_less_equal(subject)
44
+ visit(subject.attribute)
45
+ visit(subject.value)
46
+ end
47
+
48
+ def visit_criteria_contains(subject)
49
+ visit(subject.attribute)
50
+ visit(subject.value)
51
+ end
52
+
53
+ def visit_criteria_starts(subject)
54
+ visit(subject.attribute)
55
+ visit(subject.value)
56
+ end
57
+
58
+ def visit_criteria_greater_and_less(subject)
59
+ visit(subject.attribute)
60
+ visit(subject.left_value)
61
+ visit(subject.right_value)
62
+ end
63
+
64
+ def visit_criteria_greater_equal_and_less(subject)
65
+ visit(subject.attribute)
66
+ visit(subject.left_value)
67
+ visit(subject.right_value)
68
+ end
69
+
70
+ def visit_criteria_greater_and_less_equal(subject)
71
+ visit(subject.attribute)
72
+ visit(subject.left_value)
73
+ visit(subject.right_value)
74
+ end
75
+
76
+ def visit_criteria_greater_equal_and_less_equal(subject)
77
+ visit(subject.attribute)
78
+ visit(subject.left_value)
79
+ visit(subject.right_value)
80
+ end
81
+
82
+ def visit_operations_not(subject)
83
+ visit(subject.operand)
84
+ end
85
+
86
+ def visit_operations_and(subject)
87
+ subject.operands.each { |o| visit(o) }
88
+ end
89
+
90
+ def visit_operations_or(subject)
91
+ subject.operands.each { |o| visit(o) }
92
+ end
93
+
94
+ def visit_operations_ascending(subject)
95
+ visit(subject.attribute)
96
+ end
97
+
98
+ def visit_operations_descending(subject)
99
+ visit(subject.attribute)
100
+ end
101
+
102
+ def visit_expressions_where(subject)
103
+ visit(subject.criterion)
104
+ end
105
+
106
+ def visit_expressions_select(subject)
107
+ subject.projections.each { |c| visit(c) }
108
+ end
109
+
110
+ def visit_expressions_order(subject)
111
+ subject.qualifiers.each { |c| visit(c) }
112
+ end
113
+
114
+ def visit_expressions_limit(subject)
115
+ visit(subject.limit)
116
+ end
117
+
118
+ def visit_expressions_offset(subject)
119
+ visit(subject.offset)
120
+ end
121
+
122
+ def visit_expressions_scroll_cursor(subject)
123
+ visit(subject.scroll_cursor)
124
+ end
125
+
126
+ def visit_attribute(subject)
127
+ if value_for_key(subject.context.index.schema, subject.name).nil?
128
+ subject.context.query.errors[subject.class.name] << attribute_error(subject)
129
+ @valid = false
130
+ end
131
+ end
132
+
133
+ def visit_value(subject)
134
+ case subject.type
135
+ when :integer
136
+ unless subject.value.is_a?(Fixnum)
137
+ subject.context.query.errors[subject.class.name] << value_error(subject)
138
+ @valid = false
139
+ end
140
+ when :double
141
+ unless subject.value.is_a?(Float)
142
+ subject.context.query.errors[subject.class.name] << value_error(subject)
143
+ @valid = false
144
+ end
145
+ when :string,:string_array,:text,:text_array
146
+ unless subject.value.is_a?(String)
147
+ subject.context.query.errors[subject.class.name] << value_error(subject)
148
+ @valid = false
149
+ end
150
+ when :date
151
+ unless subject.value.is_a?(Time)
152
+ subject.context.query.errors[subject.class.name] << value_error(subject)
153
+ @valid = false
154
+ end
155
+ when :boolean
156
+ unless subject.value.is_a?(TrueClass) || subject.value.is_a?(FalseClass)
157
+ subject.context.query.errors[subject.class.name] << value_error(subject)
158
+ @valid = false
159
+ end
160
+ else
161
+ true
162
+ end
163
+ end
164
+
165
+ def value_error(subject)
166
+ "Value #{subject.value} for #{subject.associated_attribute.name} in #{subject.parent.class.name} is defined as #{subject.type} type in schema"
167
+ end
168
+
169
+ def attribute_error(subject)
170
+ "Attribute '#{subject.name}' in #{subject.parent.class.name} missing from schema"
171
+ end
172
+ end
173
+ end
174
+ end