agnostic_backend 0.9.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 +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