params_ready_rails5 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/lib/arel/cte_name.rb +20 -0
  3. data/lib/params_ready/builder.rb +161 -0
  4. data/lib/params_ready/error.rb +31 -0
  5. data/lib/params_ready/extensions/class_reader_writer.rb +33 -0
  6. data/lib/params_ready/extensions/collection.rb +43 -0
  7. data/lib/params_ready/extensions/delegation.rb +25 -0
  8. data/lib/params_ready/extensions/finalizer.rb +26 -0
  9. data/lib/params_ready/extensions/freezer.rb +49 -0
  10. data/lib/params_ready/extensions/hash.rb +46 -0
  11. data/lib/params_ready/extensions/late_init.rb +38 -0
  12. data/lib/params_ready/extensions/registry.rb +44 -0
  13. data/lib/params_ready/extensions/undefined.rb +23 -0
  14. data/lib/params_ready/format.rb +132 -0
  15. data/lib/params_ready/helpers/arel_builder.rb +68 -0
  16. data/lib/params_ready/helpers/callable.rb +14 -0
  17. data/lib/params_ready/helpers/conditional_block.rb +31 -0
  18. data/lib/params_ready/helpers/find_in_hash.rb +22 -0
  19. data/lib/params_ready/helpers/interface_definer.rb +48 -0
  20. data/lib/params_ready/helpers/key_map.rb +176 -0
  21. data/lib/params_ready/helpers/memo.rb +41 -0
  22. data/lib/params_ready/helpers/options.rb +107 -0
  23. data/lib/params_ready/helpers/parameter_definer_class_methods.rb +39 -0
  24. data/lib/params_ready/helpers/parameter_storage_class_methods.rb +63 -0
  25. data/lib/params_ready/helpers/parameter_user_class_methods.rb +35 -0
  26. data/lib/params_ready/helpers/relation_builder_wrapper.rb +35 -0
  27. data/lib/params_ready/helpers/rule.rb +76 -0
  28. data/lib/params_ready/helpers/storage.rb +30 -0
  29. data/lib/params_ready/helpers/usage_rule.rb +36 -0
  30. data/lib/params_ready/input_context.rb +31 -0
  31. data/lib/params_ready/intent.rb +70 -0
  32. data/lib/params_ready/marshaller/array_marshallers.rb +132 -0
  33. data/lib/params_ready/marshaller/builder_module.rb +9 -0
  34. data/lib/params_ready/marshaller/collection.rb +165 -0
  35. data/lib/params_ready/marshaller/definition_module.rb +63 -0
  36. data/lib/params_ready/marshaller/enum_set_marshallers.rb +96 -0
  37. data/lib/params_ready/marshaller/parameter_module.rb +11 -0
  38. data/lib/params_ready/marshaller/polymorph_marshallers.rb +67 -0
  39. data/lib/params_ready/marshaller/struct_marshallers.rb +100 -0
  40. data/lib/params_ready/marshaller/tuple_marshallers.rb +103 -0
  41. data/lib/params_ready/ordering/column.rb +60 -0
  42. data/lib/params_ready/ordering/ordering.rb +276 -0
  43. data/lib/params_ready/output_parameters.rb +138 -0
  44. data/lib/params_ready/pagination/abstract_pagination.rb +18 -0
  45. data/lib/params_ready/pagination/cursor.rb +171 -0
  46. data/lib/params_ready/pagination/direction.rb +148 -0
  47. data/lib/params_ready/pagination/keyset_pagination.rb +254 -0
  48. data/lib/params_ready/pagination/keysets.rb +70 -0
  49. data/lib/params_ready/pagination/nulls.rb +31 -0
  50. data/lib/params_ready/pagination/offset_pagination.rb +130 -0
  51. data/lib/params_ready/pagination/tendency.rb +28 -0
  52. data/lib/params_ready/parameter/abstract_struct_parameter.rb +204 -0
  53. data/lib/params_ready/parameter/array_parameter.rb +197 -0
  54. data/lib/params_ready/parameter/definition.rb +272 -0
  55. data/lib/params_ready/parameter/enum_set_parameter.rb +102 -0
  56. data/lib/params_ready/parameter/parameter.rb +475 -0
  57. data/lib/params_ready/parameter/polymorph_parameter.rb +172 -0
  58. data/lib/params_ready/parameter/state.rb +132 -0
  59. data/lib/params_ready/parameter/struct_parameter.rb +64 -0
  60. data/lib/params_ready/parameter/tuple_parameter.rb +152 -0
  61. data/lib/params_ready/parameter/value_parameter.rb +186 -0
  62. data/lib/params_ready/parameter_definer.rb +14 -0
  63. data/lib/params_ready/parameter_user.rb +35 -0
  64. data/lib/params_ready/query/array_grouping.rb +68 -0
  65. data/lib/params_ready/query/custom_predicate.rb +102 -0
  66. data/lib/params_ready/query/exists_predicate.rb +103 -0
  67. data/lib/params_ready/query/fixed_operator_predicate.rb +77 -0
  68. data/lib/params_ready/query/grouping.rb +177 -0
  69. data/lib/params_ready/query/join_clause.rb +87 -0
  70. data/lib/params_ready/query/nullness_predicate.rb +71 -0
  71. data/lib/params_ready/query/polymorph_predicate.rb +77 -0
  72. data/lib/params_ready/query/predicate.rb +203 -0
  73. data/lib/params_ready/query/predicate_operator.rb +132 -0
  74. data/lib/params_ready/query/relation.rb +337 -0
  75. data/lib/params_ready/query/structured_grouping.rb +58 -0
  76. data/lib/params_ready/query/variable_operator_predicate.rb +125 -0
  77. data/lib/params_ready/query_context.rb +21 -0
  78. data/lib/params_ready/restriction.rb +252 -0
  79. data/lib/params_ready/result.rb +109 -0
  80. data/lib/params_ready/value/coder.rb +210 -0
  81. data/lib/params_ready/value/constraint.rb +198 -0
  82. data/lib/params_ready/value/custom.rb +56 -0
  83. data/lib/params_ready/value/validator.rb +81 -0
  84. data/lib/params_ready/version.rb +7 -0
  85. data/lib/params_ready.rb +28 -0
  86. metadata +227 -0
@@ -0,0 +1,14 @@
1
+ require_relative 'helpers/parameter_definer_class_methods'
2
+ require_relative 'helpers/parameter_storage_class_methods'
3
+ require_relative 'helpers/relation_builder_wrapper'
4
+
5
+ module ParamsReady
6
+ module ParameterDefiner
7
+ def self.included(base)
8
+ base.extend(Helpers::ParameterStorageClassMethods)
9
+ base.extend(Helpers::ParameterDefinerClassMethods)
10
+ end
11
+ end
12
+ end
13
+
14
+
@@ -0,0 +1,35 @@
1
+ require_relative 'result'
2
+ require_relative 'helpers/usage_rule'
3
+ require_relative 'helpers/options'
4
+ require_relative 'helpers/parameter_user_class_methods'
5
+ require_relative 'helpers/parameter_storage_class_methods'
6
+ require_relative 'parameter/state'
7
+
8
+ module ParamsReady
9
+ module ParameterUser
10
+ def self.included(base)
11
+ base.extend(Helpers::ParameterStorageClassMethods)
12
+ base.extend(Helpers::ParameterUserClassMethods)
13
+ end
14
+
15
+ protected
16
+
17
+ def parameter_definition(key)
18
+ self.class.parameter_definition key
19
+ end
20
+
21
+ def relation_definition(key)
22
+ self.class.relation_definition key
23
+ end
24
+
25
+ def populate_state_for(key, params, context = Format.instance(:frontend), validator = nil)
26
+ definition = create_state_for key
27
+ result, state = definition.from_input(params || {}, context: context, validator: validator || Result.new(:params_ready))
28
+ [result, state]
29
+ end
30
+
31
+ def create_state_for(key)
32
+ self.class.params_ready_option.create_state_for(key)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../parameter/struct_parameter'
2
+ require_relative '../parameter/array_parameter'
3
+ require_relative 'grouping'
4
+ require_relative 'predicate'
5
+
6
+ module ParamsReady
7
+ module Query
8
+ class ArrayGrouping < Parameter::StructParameter
9
+ include Parameter::GroupingLike
10
+
11
+ def predicates
12
+ self[:array].to_a
13
+ end
14
+
15
+ def operator
16
+ self[:operator].unwrap
17
+ end
18
+
19
+ def context_for_predicates(restriction)
20
+ restriction.for_children(self)
21
+ end
22
+
23
+ def to_query(arel_table, context: Restriction.blanket_permission)
24
+ array = self[:array]
25
+
26
+ context = array.intent_for_children(context)
27
+ super arel_table, context: context
28
+ end
29
+ end
30
+
31
+ class ArrayGroupingBuilder < Builder
32
+ include Parameter::AbstractStructParameterBuilder::StructLike
33
+ PredicateRegistry.register_predicate :array_grouping_predicate, self
34
+
35
+ def prototype(type_name, name = :proto, *arr, **opts, &block)
36
+ prototype = PredicateRegistry.predicate(type_name).instance(name, *arr, **opts)
37
+ prototype.instance_eval(&block) unless block.nil?
38
+ @definition.set_prototype prototype.build
39
+ end
40
+
41
+ def self.instance(name, altn: nil)
42
+ new ArrayGroupingDefinition.new(name, altn: altn)
43
+ end
44
+
45
+ def operator(&block)
46
+ definition = Builder.define_grouping_operator(:operator, altn: :op, &block)
47
+ add definition
48
+ end
49
+ end
50
+
51
+ class ArrayGroupingDefinition < Parameter::StructParameterDefinition
52
+ def initialize(*args, **opts)
53
+ super
54
+ end
55
+
56
+ late_init :prototype, getter: false, obligatory: false do |prototype|
57
+ array = Builder.define_array(:array, altn: :a) do
58
+ prototype(prototype)
59
+ default []
60
+ end
61
+ add_child array
62
+ Extensions::Undefined
63
+ end
64
+
65
+ parameter_class ArrayGrouping
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,102 @@
1
+ require_relative 'predicate'
2
+ require_relative '../parameter/parameter'
3
+ require_relative '../parameter/definition'
4
+
5
+ module ParamsReady
6
+ module Query
7
+ class CustomPredicate < Parameter::AbstractParameter
8
+ include Predicate::DelegatingPredicate
9
+ include Predicate::HavingChildren
10
+
11
+ def initialize(definition)
12
+ super definition
13
+ @data = definition.type.create
14
+ end
15
+
16
+ def eligible_for_query?(arel_table, context)
17
+ return false unless context.permitted? self
18
+ eligibility_test = definition.eligibility_test
19
+ return true if eligibility_test.nil?
20
+
21
+ instance_exec(arel_table, context, &eligibility_test)
22
+ end
23
+
24
+ def to_query(arel_table, context: Restriction.blanket_permission)
25
+ return unless eligible_for_query?(arel_table, context)
26
+
27
+ to_query = definition.to_query
28
+ raise ParamsReadyError, "Method 'to_query' unimplemented in '#{name}'" if to_query.nil?
29
+ result = instance_exec(arel_table, context, &to_query)
30
+
31
+ case result
32
+ when Arel::Nodes::Node, nil
33
+ result
34
+ else
35
+ literal = Arel::Nodes::SqlLiteral.new(result)
36
+ grouping = Arel::Nodes::Grouping.new(literal)
37
+ grouping
38
+ end
39
+ end
40
+
41
+ def test(record)
42
+ test = definition.test
43
+ raise ParamsReadyError, "Method 'test' unimplemented in '#{name}'" if test.nil?
44
+ self.instance_exec(record, &test)
45
+ end
46
+ end
47
+
48
+ class CustomPredicateBuilder < Builder
49
+ PredicateRegistry.register_predicate :custom_predicate, self
50
+ include AbstractPredicateBuilder::HavingType
51
+
52
+ def initialize(name, altn: nil)
53
+ super CustomPredicateDefinition.new(name, altn: altn)
54
+ end
55
+
56
+ def type_builder_instance(type_name, name, *args, altn:, **opts, &block)
57
+ builder_class = Builder.builder(type_name)
58
+ builder_class.instance(name, *args, altn: altn, **opts)
59
+ end
60
+
61
+ def data_object_handles
62
+ [@definition.name, @definition.altn]
63
+ end
64
+
65
+ def to_query(&proc)
66
+ @definition.set_to_query(proc)
67
+ end
68
+
69
+ def eligible(&proc)
70
+ @definition.set_eligibility_test(proc)
71
+ end
72
+
73
+ def test(&proc)
74
+ @definition.set_test(proc)
75
+ end
76
+ end
77
+
78
+ class CustomPredicateDefinition < Parameter::AbstractDefinition
79
+ late_init :type, obligatory: true, freeze: false
80
+ freeze_variable :type
81
+ late_init :to_query, obligatory: false
82
+ late_init :eligibility_test, obligatory: false
83
+ late_init :test, obligatory: false
84
+
85
+ include Parameter::DelegatingDefinition[:type]
86
+
87
+ def initialize(*args, type: nil, to_query: nil, test: nil, **opts)
88
+ @type = type
89
+ @to_query = to_query
90
+ @test = test
91
+ super *args, **opts
92
+ end
93
+
94
+ def finish
95
+ @type.finish
96
+ super
97
+ end
98
+
99
+ parameter_class CustomPredicate
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,103 @@
1
+ require_relative 'predicate'
2
+ require_relative 'structured_grouping'
3
+ require_relative 'join_clause'
4
+
5
+ module ParamsReady
6
+ module Query
7
+ class ExistsPredicate < StructuredGrouping
8
+ include Predicate::HavingAssociations
9
+
10
+ def to_query(query_table, context: Restriction.blanket_permission)
11
+ query_table = definition.outer_table || query_table
12
+
13
+ subquery_table = self.definition.arel_table
14
+ raise ParamsReadyError, "Arel table for '#{name}' not set" if subquery_table.nil?
15
+
16
+ predicates = predicate_group(subquery_table, context: context)
17
+
18
+ join_clause = self.related query_table, subquery_table, context
19
+ subquery = GroupingOperator.instance(:and).connect(predicates, join_clause)
20
+ select = subquery_table.where(subquery)
21
+ query = select.take(1).project(Arel.star).exists
22
+ if definition.has_child?(:existence) && self[:existence].unwrap == :none
23
+ query.not
24
+ else
25
+ query
26
+ end
27
+ end
28
+
29
+ def related(query_table, subquery_table, context)
30
+ return nil if definition.related.nil?
31
+
32
+ grouping = definition.related.to_arel(query_table, subquery_table, context, self)
33
+ subquery_table.grouping(grouping)
34
+ end
35
+
36
+ def test(record)
37
+ return nil unless is_definite?
38
+
39
+ collection = dig(record, definition.path_to_collection)
40
+
41
+ result = if collection.nil?
42
+ false
43
+ else
44
+ collection.any? do |item|
45
+ super item
46
+ end
47
+ end
48
+
49
+ if definition.has_child?(:existence) && self[:existence].unwrap == :none
50
+ !result
51
+ else
52
+ result
53
+ end
54
+ end
55
+ end
56
+
57
+ class ExistsPredicateBuilder < Builder
58
+ PredicateRegistry.register_predicate :exists_predicate, self
59
+
60
+ include GroupingLike
61
+ include Parameter::AbstractStructParameterBuilder::StructLike
62
+ include HavingArelTable
63
+
64
+ def self.instance(name, altn: nil, coll: nil)
65
+ new ExistsPredicateDefinition.new(name, altn: altn, path_to_collection: Array(coll))
66
+ end
67
+
68
+ def related(on: nil, eq: nil, &block)
69
+ join_statement = JoinStatement.new(on: on, eq: eq, &block)
70
+ @definition.set_related(join_statement)
71
+ end
72
+
73
+ def outer_table(arel_table)
74
+ @definition.set_outer_table arel_table
75
+ end
76
+
77
+ def existence(&block)
78
+ definition = Builder.define_symbol(:existence, altn: :ex) do
79
+ constrain :enum, [:some, :none]
80
+ include &block
81
+ end
82
+ add definition
83
+ end
84
+ end
85
+
86
+ class ExistsPredicateDefinition < StructuredGroupingDefinition
87
+ late_init :outer_table, obligatory: false, freeze: false
88
+ late_init :arel_table, obligatory: false, freeze: false
89
+ late_init :related, obligatory: false, freeze: true
90
+
91
+ def initialize(*args, path_to_collection: nil, **opts)
92
+ @path_to_collection = path_to_collection
93
+ super *args, **opts
94
+ end
95
+
96
+ def path_to_collection
97
+ @path_to_collection || [@name]
98
+ end
99
+
100
+ parameter_class ExistsPredicate
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,77 @@
1
+ require 'forwardable'
2
+
3
+ require_relative '../parameter/value_parameter'
4
+ require_relative '../parameter/array_parameter'
5
+ require_relative 'predicate'
6
+ require_relative 'predicate_operator'
7
+
8
+ module ParamsReady
9
+ module Query
10
+ class FixedOperatorPredicate < Parameter::AbstractParameter
11
+ include Predicate::DelegatingPredicate
12
+ include Predicate::HavingAttribute
13
+
14
+ def initialize(definition, **options)
15
+ super definition
16
+ @data = definition.type.create
17
+ end
18
+
19
+ def build_query(select_expression, context: nil)
20
+ definition.operator.to_query(select_expression, @data.unwrap)
21
+ end
22
+
23
+ def perform_test(record, attribute_name)
24
+ definition.operator.test(record, attribute_name, @data.unwrap)
25
+ end
26
+
27
+ def inspect_content
28
+ op = definition.operator.name
29
+ "#{definition.attribute_name} #{op} #{@data.inspect}"
30
+ end
31
+ end
32
+
33
+ class FixedOperatorPredicateBuilder < AbstractPredicateBuilder
34
+ PredicateRegistry.register_predicate :fixed_operator_predicate, self
35
+ include HavingType
36
+ include HavingValue
37
+ include HavingAttribute
38
+
39
+ def self.instance(name, altn: nil, attr: nil)
40
+ new FixedOperatorPredicateDefinition.new name, altn: altn, attribute_name: attr
41
+ end
42
+
43
+ def data_object_handles
44
+ [@definition.name, @definition.altn]
45
+ end
46
+
47
+ def operator(name)
48
+ operator = PredicateRegistry.operator name, Format.instance(:backend)
49
+ @definition.set_operator operator
50
+ end
51
+ end
52
+
53
+ class FixedOperatorPredicateDefinition < AbstractPredicateDefinition
54
+ extend Forwardable
55
+ include HavingAttribute
56
+ include Parameter::DelegatingDefinition[:type]
57
+
58
+ late_init :operator, obligatory: true
59
+ late_init :type, obligatory: true, freeze: false
60
+
61
+ def initialize(*args, attribute_name: nil, type: nil, operator: nil, **opts)
62
+ @attribute_name = attribute_name
63
+ @type = type
64
+ @operator = operator
65
+ @associations = []
66
+ super *args, **opts
67
+ end
68
+
69
+ def finish
70
+ @type.finish
71
+ super
72
+ end
73
+
74
+ parameter_class FixedOperatorPredicate
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,177 @@
1
+ require_relative 'join_clause'
2
+ require_relative '../parameter/parameter'
3
+ require_relative '../parameter/value_parameter'
4
+
5
+ module ParamsReady
6
+ class Builder
7
+ module GroupingLike
8
+ def predicate_builder(name)
9
+ symbol = name.to_sym
10
+ return nil unless Query::PredicateRegistry.has_predicate?(symbol)
11
+
12
+ Query::PredicateRegistry.predicate(symbol)
13
+ end
14
+
15
+ def method_missing(name, *args, **opts, &proc)
16
+ builder_class = predicate_builder(name)
17
+ if builder_class
18
+ builder = builder_class.instance *args, **opts
19
+ build_predicate builder, &proc
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def respond_to_missing?(name, include_private = false)
26
+ return true unless predicate_builder(name).nil?
27
+
28
+ super
29
+ end
30
+
31
+ def build_predicate(builder, &proc)
32
+ builder.instance_eval(&proc)
33
+ definition = builder.build
34
+ add_predicate definition
35
+ end
36
+
37
+ def add_predicate(name_or_definition, *args, **opts, &block)
38
+ if name_or_definition.is_a? Parameter::AbstractDefinition
39
+ @definition.add_predicate name_or_definition
40
+ add name_or_definition
41
+ else
42
+ builder = predicate_builder(name_or_definition).instance *args, **opts
43
+ build_predicate builder, &block
44
+ end
45
+ end
46
+
47
+ def operator(&block)
48
+ definition = Builder.define_grouping_operator(:operator, altn: :op, &block)
49
+ add definition
50
+ end
51
+ end
52
+ end
53
+
54
+ module Parameter
55
+ module GroupingLike
56
+ def predicate_group(arel_table, context: Restriction.blanket_permission)
57
+ subqueries = predicates.reduce(nil) do |acc, predicate|
58
+ query = predicate.to_query_if_eligible(arel_table, context: context)
59
+ # This duplicates the operator logic
60
+ # but we want operator to be optional
61
+ # for single predicate groupings
62
+ next query if acc.nil?
63
+
64
+ operator.connect(acc, query)
65
+ end
66
+ return nil if subqueries.nil?
67
+ arel_table.grouping(subqueries)
68
+ end
69
+
70
+ def eligible_for_query?(_table, context)
71
+ return false unless context.permitted? self
72
+
73
+ is_definite?
74
+ end
75
+
76
+ def to_query_if_eligible(arel_table, context:)
77
+ return nil unless eligible_for_query?(arel_table, context)
78
+
79
+ context = context_for_predicates(context)
80
+ to_query(arel_table, context: context)
81
+ end
82
+
83
+ def to_query(arel_table, context: Restriction.blanket_permission)
84
+ self.predicate_group(arel_table, context: context)
85
+ end
86
+
87
+ def test(record)
88
+ return nil unless is_definite?
89
+
90
+ predicates = self.predicates
91
+ return nil if predicates.empty?
92
+
93
+ operator.test(record, predicates)
94
+ end
95
+ end
96
+ end
97
+
98
+ module Query
99
+ class GroupingOperatorCoder < Value::SymbolCoder
100
+ def self.coerce(value, _)
101
+ return value if value.is_a? GroupingOperator
102
+
103
+ symbol = super
104
+ GroupingOperator.instance(symbol)
105
+ end
106
+
107
+ def self.format(value, _)
108
+ value.type.to_s
109
+ end
110
+
111
+ def self.strict_default?
112
+ false
113
+ end
114
+ end
115
+
116
+ Parameter::ValueParameterBuilder.register_coder :grouping_operator, GroupingOperatorCoder
117
+
118
+ class GroupingOperator
119
+ attr_reader :type
120
+
121
+ def self.instance(type)
122
+ raise ParamsReadyError, "Unimplemented operator: #{type}" unless @instances.key? type
123
+ @instances[type]
124
+ end
125
+
126
+ def arel_method
127
+ case type
128
+ when :and, :or then type
129
+ else
130
+ raise ParamsReadyError, "Unimplemented operator: #{type}"
131
+ end
132
+ end
133
+
134
+ def test_method
135
+ case type
136
+ when :and then :all?
137
+ when :or then :any?
138
+ else
139
+ raise ParamsReadyError, "Unimplemented operator: #{type}"
140
+ end
141
+ end
142
+
143
+ def connect(a, b)
144
+ return b if a.nil?
145
+ return a if b.nil?
146
+ a.send arel_method, b
147
+ end
148
+
149
+ def ==(other)
150
+ return false unless other.class <= GroupingOperator
151
+ type == other.type
152
+ end
153
+
154
+ def test(record, predicates)
155
+ definite = predicates.map do |predicate|
156
+ predicate.test(record)
157
+ end.compact
158
+
159
+ return nil if definite.empty?
160
+
161
+ definite.send(test_method)
162
+ end
163
+
164
+ protected
165
+
166
+ def initialize(type)
167
+ @type = type
168
+ end
169
+
170
+ @instances = {}
171
+ @instances[:and] = GroupingOperator.new(:and)
172
+ @instances[:or] = GroupingOperator.new(:or)
173
+
174
+ private_class_method :new
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,87 @@
1
+ require_relative '../helpers/arel_builder'
2
+
3
+ module ParamsReady
4
+ module Query
5
+ class Join
6
+ attr_reader :arel_table, :statement, :type
7
+
8
+ def initialize(table, type, &block)
9
+ @arel_table = table
10
+ @type = arel_type(type)
11
+ @statement = JoinStatement.new(&block)
12
+ end
13
+
14
+ def arel_type(type)
15
+ case type
16
+ when :inner then Arel::Nodes::InnerJoin
17
+ when :outer then Arel::Nodes::OuterJoin
18
+ else raise ParamsReadyError, "Unimplemented join type '#{type}'"
19
+ end
20
+ end
21
+
22
+ def to_arel(joined_table, base_table, context, parameter)
23
+ join_statement = @statement.to_arel(base_table, @arel_table, context, parameter)
24
+ joined_table.join(@arel_table, @type).on(join_statement)
25
+ end
26
+ end
27
+
28
+ class JoinStatement
29
+ def initialize(on: nil, eq: nil, &block)
30
+ @conditions = []
31
+ if on
32
+ condition = on(on)
33
+ if eq
34
+ condition.eq(eq)
35
+ end
36
+ else
37
+ raise ParamsReadyError('Parameter :eq unexpected') unless eq.nil?
38
+ end
39
+
40
+ instance_eval(&block) unless block.nil?
41
+ raise ParamsReadyError, "Join clause is empty" if @conditions.empty?
42
+ end
43
+
44
+ def on(expression, arel_table: nil)
45
+ condition = JoinCondition.new(expression, arel_table: arel_table)
46
+ @conditions << condition
47
+ condition
48
+ end
49
+
50
+ def to_arel(base_table, join_table, context, parameter)
51
+ @conditions.reduce(nil) do |result, condition|
52
+ arel = condition.to_arel(base_table, join_table, context, parameter)
53
+ next arel if result.nil?
54
+
55
+ result.and(arel)
56
+ end
57
+ end
58
+ end
59
+
60
+ class JoinCondition
61
+ def initialize(expression, arel_table: nil)
62
+ @on = Helpers::ArelBuilder.instance(expression, arel_table: arel_table)
63
+ @to = nil
64
+ @op = nil
65
+ end
66
+
67
+ def eq(expression, arel_table: nil)
68
+ raise ParamsReadyError, "Operator already set" unless @op.nil?
69
+ @op = :eq
70
+ @to = Helpers::ArelBuilder.instance(expression, arel_table: arel_table)
71
+ end
72
+
73
+ def to_arel(base_table, join_table, context, parameter)
74
+ if @to.nil?
75
+ grouping = @on.to_arel(:none, context, parameter)
76
+ return grouping if grouping.is_a? Arel::Nodes::Node
77
+
78
+ Arel::Nodes::Grouping.new(grouping)
79
+ else
80
+ lhs = @on.to_arel(base_table, context, parameter)
81
+ rhs = @to.to_arel(join_table, context, parameter)
82
+ lhs.send(@op, rhs)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end