params_ready_rails5 0.0.7

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 (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