params_ready 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/lib/arel/cte_name.rb +20 -0
  3. data/lib/params_ready.rb +36 -0
  4. data/lib/params_ready/builder.rb +140 -0
  5. data/lib/params_ready/error.rb +31 -0
  6. data/lib/params_ready/extensions/class_reader_writer.rb +33 -0
  7. data/lib/params_ready/extensions/collection.rb +43 -0
  8. data/lib/params_ready/extensions/delegation.rb +25 -0
  9. data/lib/params_ready/extensions/finalizer.rb +26 -0
  10. data/lib/params_ready/extensions/freezer.rb +49 -0
  11. data/lib/params_ready/extensions/hash.rb +46 -0
  12. data/lib/params_ready/extensions/late_init.rb +38 -0
  13. data/lib/params_ready/extensions/registry.rb +44 -0
  14. data/lib/params_ready/extensions/undefined.rb +15 -0
  15. data/lib/params_ready/format.rb +130 -0
  16. data/lib/params_ready/helpers/arel_builder.rb +68 -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/key_map.rb +176 -0
  20. data/lib/params_ready/helpers/memo.rb +42 -0
  21. data/lib/params_ready/helpers/options.rb +39 -0
  22. data/lib/params_ready/helpers/parameter_definer_class_methods.rb +39 -0
  23. data/lib/params_ready/helpers/parameter_storage_class_methods.rb +36 -0
  24. data/lib/params_ready/helpers/parameter_user_class_methods.rb +31 -0
  25. data/lib/params_ready/helpers/relation_builder_wrapper.rb +35 -0
  26. data/lib/params_ready/helpers/rule.rb +57 -0
  27. data/lib/params_ready/helpers/storage.rb +30 -0
  28. data/lib/params_ready/helpers/usage_rule.rb +18 -0
  29. data/lib/params_ready/input_context.rb +31 -0
  30. data/lib/params_ready/intent.rb +70 -0
  31. data/lib/params_ready/marshaller/array_marshallers.rb +132 -0
  32. data/lib/params_ready/marshaller/builder_module.rb +9 -0
  33. data/lib/params_ready/marshaller/collection.rb +165 -0
  34. data/lib/params_ready/marshaller/definition_module.rb +63 -0
  35. data/lib/params_ready/marshaller/hash_marshallers.rb +100 -0
  36. data/lib/params_ready/marshaller/hash_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/tuple_marshallers.rb +103 -0
  40. data/lib/params_ready/ordering/column.rb +60 -0
  41. data/lib/params_ready/ordering/ordering.rb +276 -0
  42. data/lib/params_ready/output_parameters.rb +127 -0
  43. data/lib/params_ready/pagination/abstract_pagination.rb +18 -0
  44. data/lib/params_ready/pagination/cursor.rb +171 -0
  45. data/lib/params_ready/pagination/direction.rb +148 -0
  46. data/lib/params_ready/pagination/keyset_pagination.rb +254 -0
  47. data/lib/params_ready/pagination/keysets.rb +70 -0
  48. data/lib/params_ready/pagination/nulls.rb +31 -0
  49. data/lib/params_ready/pagination/offset_pagination.rb +130 -0
  50. data/lib/params_ready/pagination/tendency.rb +28 -0
  51. data/lib/params_ready/parameter/abstract_hash_parameter.rb +204 -0
  52. data/lib/params_ready/parameter/array_parameter.rb +197 -0
  53. data/lib/params_ready/parameter/definition.rb +264 -0
  54. data/lib/params_ready/parameter/hash_parameter.rb +63 -0
  55. data/lib/params_ready/parameter/hash_set_parameter.rb +101 -0
  56. data/lib/params_ready/parameter/parameter.rb +456 -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/tuple_parameter.rb +152 -0
  60. data/lib/params_ready/parameter/value_parameter.rb +182 -0
  61. data/lib/params_ready/parameter_definer.rb +14 -0
  62. data/lib/params_ready/parameter_user.rb +43 -0
  63. data/lib/params_ready/query/array_grouping.rb +68 -0
  64. data/lib/params_ready/query/custom_predicate.rb +102 -0
  65. data/lib/params_ready/query/exists_predicate.rb +103 -0
  66. data/lib/params_ready/query/fixed_operator_predicate.rb +77 -0
  67. data/lib/params_ready/query/grouping.rb +177 -0
  68. data/lib/params_ready/query/join_clause.rb +87 -0
  69. data/lib/params_ready/query/nullness_predicate.rb +71 -0
  70. data/lib/params_ready/query/polymorph_predicate.rb +77 -0
  71. data/lib/params_ready/query/predicate.rb +203 -0
  72. data/lib/params_ready/query/predicate_operator.rb +132 -0
  73. data/lib/params_ready/query/relation.rb +337 -0
  74. data/lib/params_ready/query/structured_grouping.rb +58 -0
  75. data/lib/params_ready/query/variable_operator_predicate.rb +125 -0
  76. data/lib/params_ready/query_context.rb +21 -0
  77. data/lib/params_ready/restriction.rb +252 -0
  78. data/lib/params_ready/result.rb +109 -0
  79. data/lib/params_ready/value/coder.rb +181 -0
  80. data/lib/params_ready/value/constraint.rb +198 -0
  81. data/lib/params_ready/value/custom.rb +56 -0
  82. data/lib/params_ready/value/validator.rb +68 -0
  83. metadata +181 -0
@@ -0,0 +1,203 @@
1
+ require_relative '../extensions/registry'
2
+ require_relative '../parameter/array_parameter'
3
+ require_relative '../parameter/hash_set_parameter'
4
+ require_relative '../helpers/arel_builder'
5
+
6
+ module ParamsReady
7
+ module Query
8
+ class AbstractPredicateBuilder < AbstractBuilder
9
+ module HavingType
10
+ def type(type_name, *args, **opts, &block)
11
+ name, altn = data_object_handles
12
+ builder = type_builder_instance(type_name, name, *args, altn: altn, **opts)
13
+ builder.instance_eval(&block) unless block.nil?
14
+ @definition.set_type builder.fetch
15
+ end
16
+
17
+ def type_builder_instance(type_name, name, *args, altn:, **opts)
18
+ AbstractPredicateBuilder.type(type_name)
19
+ .instance(name, *args, altn: altn, **opts)
20
+ end
21
+ end
22
+
23
+ module HavingAttribute
24
+ def associations(*arr)
25
+ arr.each do |name|
26
+ @definition.add_association name
27
+ end
28
+ end
29
+
30
+ def attribute(name: nil, expression: nil, &block)
31
+ expression = if expression
32
+ raise ParamsReadyError, 'Block unexpected' unless block.nil?
33
+ expression
34
+ else
35
+ raise ParamsReadyError, 'Expression unexpected' unless expression.nil?
36
+ block
37
+ end
38
+ @definition.set_attribute(name, expression)
39
+ end
40
+ end
41
+
42
+ include HavingArelTable
43
+
44
+ extend Extensions::Registry
45
+ registry :types, as: :type, getter: true
46
+ register_type :value, Parameter::ValueParameterBuilder
47
+ register_type :array, Parameter::ArrayParameterBuilder
48
+ register_type :hash_set, Parameter::HashSetParameterBuilder
49
+ end
50
+
51
+ class AbstractPredicateDefinition < Parameter::AbstractDefinition
52
+ late_init :arel_table, obligatory: false, freeze: false
53
+
54
+ module HavingAttribute
55
+ def self.included(base)
56
+ base.collection :associations, :association
57
+ end
58
+
59
+ def set_attribute(name, select_expression)
60
+ @attribute_name = name
61
+ @select_expression = select_expression
62
+ end
63
+
64
+ def attribute_name
65
+ @attribute_name || @name
66
+ end
67
+
68
+ def select_expression
69
+ @select_expression || attribute_name
70
+ end
71
+
72
+ def build_select_expression(arel_table, context)
73
+ arel_builder = Helpers::ArelBuilder.instance(select_expression, arel_table: @arel_table)
74
+ arel = arel_builder.to_arel(arel_table, context, self)
75
+
76
+ arel
77
+ end
78
+
79
+ def alias_select_expression(arel_table, context)
80
+ build_select_expression(arel_table, context).as(attribute_name.to_s)
81
+ end
82
+ end
83
+ end
84
+
85
+ module Predicate
86
+ module HavingChildren
87
+ def context_for_predicates(context)
88
+ intent_for_children(context)
89
+ end
90
+ end
91
+
92
+ module HavingAssociations
93
+ def dig(record, associations)
94
+ associations.reduce(record) do |record, assoc|
95
+ next record if record.nil?
96
+
97
+ record.send assoc
98
+ end
99
+ end
100
+ end
101
+
102
+ module HavingAttribute
103
+ extend Forwardable
104
+ def_delegators :definition, :build_select_expression, :alias_select_expression
105
+ include HavingAssociations
106
+
107
+ def to_query(arel_table, context: Restriction.blanket_permission)
108
+ table = definition.arel_table || arel_table
109
+ select_expression = build_select_expression(table, context)
110
+ build_query(select_expression, context: context)
111
+ end
112
+
113
+ def context_for_predicates(context)
114
+ # We consider a an attribute having parameter atomic
115
+ # so it's permitted per se including its contents
116
+ context.permit_all
117
+ end
118
+
119
+ def test(record)
120
+ return nil unless is_definite?
121
+
122
+ attribute_name = definition.attribute_name
123
+ record = dig(record, definition.associations)
124
+
125
+ perform_test(record, attribute_name)
126
+ end
127
+ end
128
+
129
+ module DelegatingPredicate
130
+ def self.included(base)
131
+ base.include Parameter::DelegatingParameter
132
+ end
133
+
134
+ def eligible_for_query?(_table, context)
135
+ return false unless context.permitted? self
136
+
137
+ is_definite?
138
+ end
139
+
140
+ def to_query_if_eligible(arel_table, context:)
141
+ return unless eligible_for_query?(arel_table, context)
142
+
143
+ context = context_for_predicates(context)
144
+ to_query(arel_table, context: context)
145
+ end
146
+ attr_reader :data
147
+
148
+ end
149
+ end
150
+
151
+ class PredicateRegistry
152
+ extend Extensions::Registry
153
+
154
+ registry :operator_names, as: :operator_by_name, name_method: :name
155
+ registry :operator_alt_names, as: :operator_by_alt_name, name_method: :altn
156
+ registry :predicates, as: :predicate, getter: true
157
+
158
+ def self.operator_by(identifier, format)
159
+ if format.alternative?
160
+ @@operator_alt_names[identifier]
161
+ else
162
+ @@operator_names[identifier]
163
+ end
164
+ end
165
+
166
+ def self.operator(identifier, format, collision_check = false)
167
+ operator = find_operator(identifier, format, collision_check)
168
+ if operator.nil? && !collision_check
169
+ raise("No such operator: #{identifier}")
170
+ end
171
+
172
+ operator
173
+ end
174
+
175
+ def self.find_operator(identifier, format, collision_check)
176
+ operator = operator_by(identifier, format)
177
+ if operator.nil?
178
+ name_as_string = identifier.to_s
179
+ invertor = if format.alternative?
180
+ 'n_'
181
+ else
182
+ 'not_'
183
+ end
184
+ if !collision_check && name_as_string.start_with?(invertor)
185
+ bare_name = name_as_string[invertor.length..-1].to_sym
186
+ inverted = PredicateRegistry.operator(bare_name, format)
187
+ if inverted.nil?
188
+ nil
189
+ elsif inverted.inverse_of.nil?
190
+ Not.new(inverted)
191
+ else
192
+ operator(inverted.inverse_of, format)
193
+ end
194
+ else
195
+ nil
196
+ end
197
+ else
198
+ operator
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,132 @@
1
+ require_relative 'predicate'
2
+
3
+ module ParamsReady
4
+ module Query
5
+ class PredicateOperator
6
+ def self.dup
7
+ self
8
+ end
9
+
10
+ def self.define_operator(name, altn, arel: nil, test: nil, inverse_of: nil)
11
+ define_singleton_method :name do
12
+ name
13
+ end
14
+
15
+ define_singleton_method :altn do
16
+ altn
17
+ end
18
+
19
+ if arel
20
+ define_singleton_method :to_query do |attribute, value|
21
+ attribute.send(arel, value)
22
+ end
23
+ end
24
+
25
+ if test
26
+ define_singleton_method :test do |record, attribute_name, value|
27
+ attribute = record.send attribute_name
28
+ attribute.send test, value
29
+ end
30
+ end
31
+
32
+ define_singleton_method :inverse_of do
33
+ inverse_of
34
+ end
35
+
36
+ unless PredicateRegistry.operator(self.name, Format.instance(:backend), true).nil?
37
+ raise ParamsReadyError, "Operator name taken #{self.name}"
38
+ end
39
+
40
+ unless PredicateRegistry.operator(self.altn, Format.instance(:frontend), true).nil?
41
+ raise ParamsReadyError, "Operator altn taken #{self.name}"
42
+ end
43
+
44
+ PredicateRegistry.register_operator_by_name self
45
+ PredicateRegistry.register_operator_by_alt_name self
46
+ end
47
+
48
+ def altn
49
+ self.class.altn
50
+ end
51
+ end
52
+
53
+ class In < PredicateOperator
54
+ define_operator :in, :in, arel: :in
55
+
56
+ def self.test(record, attribute_name, values)
57
+ attribute = record.send attribute_name
58
+ values.include? attribute
59
+ end
60
+ end
61
+
62
+
63
+ class ComparisonPredicateOperator < PredicateOperator; end
64
+
65
+ class Like < ComparisonPredicateOperator
66
+ define_operator :like, :lk
67
+
68
+ def self.to_query(attribute_name, value)
69
+ attribute_name.matches("%#{value}%")
70
+ end
71
+
72
+ def self.test(record, attribute_name, value)
73
+ attribute = record.send attribute_name
74
+ result = Regexp.new(value, Regexp::IGNORECASE) =~ attribute
75
+ result.nil? ? false : true
76
+ end
77
+ end
78
+
79
+ class Equal < ComparisonPredicateOperator
80
+ define_operator :equal, :eq, arel: :eq, test: :==
81
+ end
82
+
83
+ class NotEqual < ComparisonPredicateOperator
84
+ define_operator :not_equal, :neq, arel: :not_eq, test: :!=
85
+ end
86
+
87
+ class GreaterThan < ComparisonPredicateOperator
88
+ define_operator :greater_than, :gt, arel: :gt, test: :>, inverse_of: :less_than_or_equal
89
+
90
+ def self.test(record, attribute_name, value)
91
+ attribute = record.send attribute_name
92
+ attribute > value
93
+ end
94
+ end
95
+
96
+ class LessThan < ComparisonPredicateOperator
97
+ define_operator :less_than, :lt, arel: :lt, test: :<, inverse_of: :greater_than_or_equal
98
+ end
99
+
100
+ class GraterThanOrEqual < ComparisonPredicateOperator
101
+ define_operator :greater_than_or_equal, :gteq, arel: :gteq, test: :>=, inverse_of: :less_than
102
+ end
103
+
104
+ class LessThanOrEqual < ComparisonPredicateOperator
105
+ define_operator :less_than_or_equal, :lteq, arel: :lteq, test: :<=, inverse_of: :greater_than
106
+ end
107
+
108
+ class Not
109
+ def initialize(operator)
110
+ @operator = operator
111
+ end
112
+
113
+ def name
114
+ "not_#{@operator.name}"
115
+ end
116
+
117
+ def altn
118
+ "n#{@operator.altn}"
119
+ end
120
+
121
+ def test(*args)
122
+ result = @operator.test *args
123
+ !result
124
+ end
125
+
126
+ def to_query(*args)
127
+ result = @operator.to_query(*args)
128
+ result.not
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,337 @@
1
+ require 'forwardable'
2
+ require_relative 'structured_grouping'
3
+ require_relative 'join_clause'
4
+ require_relative '../pagination/offset_pagination'
5
+ require_relative '../pagination/keyset_pagination'
6
+ require_relative '../pagination/direction'
7
+ require_relative '../ordering/ordering'
8
+
9
+ module ParamsReady
10
+ module Query
11
+ class Relation < StructuredGrouping
12
+ module PageAccessors
13
+ def page_accessor(name, delegate = nil)
14
+ delegate ||= "#{name}_page"
15
+
16
+ define_method name do |*args|
17
+ send(delegate, *args)&.for_frontend
18
+ end
19
+ ruby2_keywords name
20
+ end
21
+
22
+ def self.extended(mod)
23
+ mod.page_accessor :current
24
+ mod.page_accessor :first
25
+ mod.page_accessor :last
26
+ mod.page_accessor :previous
27
+ mod.page_accessor :next
28
+ mod.page_accessor :before
29
+ mod.page_accessor :after
30
+ mod.page_accessor :limit_at, :limited_at
31
+ mod.page_accessor :toggle, :toggled_order
32
+ mod.page_accessor :reorder, :reordered
33
+ end
34
+ end
35
+
36
+ extend PageAccessors
37
+ extend Forwardable
38
+
39
+ def_delegators :pagination, :offset, :limit, :num_pages, :page_no, :has_previous?, :has_next?, :has_page?
40
+
41
+ def child_is_definite?(name)
42
+ return false unless definition.has_child?(name)
43
+ return false if self[name].nil?
44
+ return false unless self[name].is_definite?
45
+
46
+ true
47
+ end
48
+
49
+ def pagination
50
+ self[:pagination]
51
+ end
52
+
53
+ def ordering
54
+ self[:ordering]
55
+ end
56
+
57
+ def ordering_or_nil
58
+ return unless child_is_definite?(:ordering)
59
+
60
+ self[:ordering]
61
+ end
62
+
63
+ def new_offset(delta)
64
+ pagination.new_offset(delta)
65
+ end
66
+
67
+ def page(delta, count: nil)
68
+ return nil unless pagination.can_yield_page?(delta, count: count)
69
+ return self if delta == 0
70
+
71
+ new_offset = pagination.new_offset(delta)
72
+
73
+ update_in(new_offset, [:pagination, 0])
74
+ end
75
+
76
+ def current_page(count: nil)
77
+ page(0, count: count)
78
+ end
79
+
80
+ def first_page
81
+ value = pagination.first_page_value
82
+ update_in(value, [:pagination])
83
+ end
84
+
85
+ def last_page(count:)
86
+ value = pagination.last_page_value(count: count)
87
+ return if value.nil?
88
+ update_in(value, [:pagination])
89
+ end
90
+
91
+ def previous_page(delta = 1)
92
+ value = pagination.previous_page_value(delta)
93
+ return if value.nil?
94
+ update_in(value, [:pagination])
95
+ end
96
+
97
+ def next_page(delta = 1, count: nil)
98
+ value = pagination.next_page_value(delta, count: count)
99
+ return if value.nil?
100
+ page(delta, count: count)
101
+ end
102
+
103
+ def before_page(keyset)
104
+ tuple = { direction: :bfr, limit: limit, keyset: keyset }
105
+ update_in(tuple, [:pagination])
106
+ end
107
+
108
+ def after_page(keyset)
109
+ tuple = { direction: :aft, limit: limit, keyset: keyset }
110
+ update_in(tuple, [:pagination])
111
+ end
112
+
113
+ def limited_at(limit)
114
+ update_in(limit, [:pagination, pagination.limit_key])
115
+ end
116
+
117
+ def toggled_order(column)
118
+ new_order = ordering.toggled_order_value(column)
119
+ toggled = update_in(new_order, [:ordering])
120
+ toggled.update_in(0, [:pagination, 0])
121
+ end
122
+
123
+ def reordered(column, direction)
124
+ new_order = ordering.reordered_value(column, direction)
125
+ reordered = update_in(new_order, [:ordering])
126
+ reordered.update_in(0, [:pagination, 0])
127
+ end
128
+
129
+ def model_class(default_model_class)
130
+ default_model_class || definition.model_class
131
+ end
132
+
133
+ def arel_table(default_model_class)
134
+ model_class(default_model_class).arel_table
135
+ end
136
+
137
+ def perform_count(scope: nil, context: Restriction.blanket_permission)
138
+ scope ||= definition.model_class if definition.model_class_defined?
139
+ group = predicate_group(scope.arel_table, context: context)
140
+ relation = scope.where(group)
141
+ relation = perform_joins(relation, context)
142
+ relation.count
143
+ end
144
+
145
+ def keysets(limit, direction, keyset, scope: nil, context: Restriction.blanket_permission, &block)
146
+ model_class = scope || definition.model_class
147
+ group = predicate_group(model_class.arel_table, context: context)
148
+ relation = model_class.where(group)
149
+ relation = perform_joins(relation, context)
150
+
151
+ sql_literal = pagination.keysets_for_relation(relation, limit, direction, keyset, ordering, context, &block)
152
+
153
+ array = model_class.connection.execute(sql_literal.to_s).to_a
154
+ Pagination::Direction.instance(direction).keysets(keyset, array, &block)
155
+ end
156
+
157
+ def build_relation(scope: nil, include: [], context: Restriction.blanket_permission, paginate: true)
158
+ model_class = scope || definition.model_class
159
+ group = predicate_group(model_class.arel_table, context: context)
160
+ relation = model_class.where(group)
161
+ relation = relation.includes(*include) unless include.empty?
162
+ relation = perform_joins(relation, context)
163
+
164
+ order_and_paginate_relation(relation, context, paginate)
165
+ end
166
+
167
+ def perform_joins(relation, context)
168
+ return relation if definition.joins.empty?
169
+
170
+ sql = joined_tables(relation.arel_table, context).join_sources.map(&:to_sql).join(' ')
171
+ relation.joins(sql)
172
+ end
173
+
174
+ def to_count(model_class: nil, context: Restriction.blanket_permission)
175
+ model_class = model_class || definition.model_class
176
+
177
+ arel_table = joined_tables(model_class.arel_table, context)
178
+
179
+ group = self.predicate_group(model_class.arel_table, context: context)
180
+
181
+ query = if group.nil?
182
+ arel_table
183
+ else
184
+ arel_table.where(group)
185
+ end
186
+ query.project(arel_table[:id].count)
187
+ end
188
+
189
+ def build_select(model_class: nil, context: Restriction.blanket_permission, select_list: Arel.star, paginate: true)
190
+ arel_table, query = build_query(model_class, context)
191
+ query = order_and_paginate_query(query, arel_table, context, paginate)
192
+ query.project(select_list)
193
+ end
194
+
195
+ def build_keyset_query(limit, direction, keyset, model_class: nil, context: Restriction.blanket_permission)
196
+ arel_table, query = build_query(model_class, context)
197
+ pagination.select_keysets(query, limit, direction, keyset, ordering, arel_table, context)
198
+ end
199
+
200
+ def build_query(model_class, context)
201
+ arel_table = arel_table(model_class)
202
+
203
+ group = self.predicate_group(arel_table, context: context)
204
+ joined = joined_tables(arel_table, context)
205
+
206
+ query = if group.nil?
207
+ joined
208
+ else
209
+ joined.where(group)
210
+ end
211
+
212
+ [arel_table, query]
213
+ end
214
+
215
+ def order_if_applicable(arel_table, context)
216
+ if child_is_definite?(:ordering) && (context.permitted?(ordering) || ordering.required?)
217
+ ordering = self.ordering.to_arel(arel_table, context: context)
218
+ yield ordering if ordering.length > 0
219
+ end
220
+ end
221
+
222
+ def paginate_if_applicable(paginate)
223
+ if paginate && child_is_definite?(:pagination)
224
+ pagination = self.pagination
225
+ yield pagination
226
+ end
227
+ end
228
+
229
+ def order_and_paginate_relation(relation, context, paginate)
230
+ paginate_if_applicable(paginate) do |pagination|
231
+ relation = pagination.paginate_relation(relation, ordering_or_nil, context)
232
+ end
233
+
234
+ order_if_applicable(relation.arel_table, context) do |ordering|
235
+ relation = relation.order(ordering)
236
+ end
237
+ relation
238
+ end
239
+
240
+ def order_and_paginate_query(query, arel_table, context, paginate)
241
+ paginate_if_applicable(paginate) do |pagination|
242
+ query = pagination.paginate_query(query, ordering_or_nil, arel_table, context)
243
+ end
244
+
245
+ order_if_applicable(arel_table, context) do |ordering|
246
+ query = query.order(*ordering)
247
+ end
248
+ query
249
+ end
250
+
251
+ def joined_tables(proper_table, context)
252
+ definition.joins.reduce(proper_table) do |table, join|
253
+ join.to_arel(table, context, self)
254
+ end
255
+ end
256
+ end
257
+
258
+ class RelationParameterBuilder < Builder
259
+ include GroupingLike
260
+ include Parameter::AbstractHashParameterBuilder::HashLike
261
+ include HavingModel
262
+
263
+ def self.instance(name, altn: nil)
264
+ new RelationDefinition.new(name, altn: altn)
265
+ end
266
+
267
+ register :relation
268
+ DEFAULT_LIMIT = 10
269
+
270
+ def paginate(limit = DEFAULT_LIMIT, max_limit = nil, method: :offset, &block)
271
+ case method
272
+ when :offset
273
+ raise ParamsReadyError, 'Block not expected' unless block.nil?
274
+ add Pagination::OffsetPaginationDefinition.new(0, limit, max_limit).finish
275
+ when :keyset
276
+ ordering_builder = @definition.init_ordering_builder(empty: true)
277
+ rcpb = Pagination::KeysetPaginationBuilder.new ordering_builder, limit, max_limit
278
+ add rcpb.build(&block)
279
+ else
280
+ raise "Unimplemented pagination method '#{method}'"
281
+ end
282
+ end
283
+
284
+ def order(&proc)
285
+ ordering_builder = @definition.init_ordering_builder(empty: false)
286
+ ordering_builder.instance_eval(&proc) unless proc.nil?
287
+ ordering = ordering_builder.build
288
+ add ordering
289
+ end
290
+
291
+ def join_table(arel_table, type, &block)
292
+ join = Join.new arel_table, type, &block
293
+ join.freeze
294
+ @definition.add_join(join)
295
+ end
296
+ end
297
+
298
+ class RelationDefinition < StructuredGroupingDefinition
299
+ late_init :model_class, obligatory: false, freeze: false, getter: false
300
+ collection :joins, :join
301
+
302
+ def model_class
303
+ raise ParamsReadyError, "Model class not set for #{name}" if @model_class.nil?
304
+ @model_class
305
+ end
306
+
307
+ def init_ordering_builder(empty:)
308
+ raise ParamsReadyError, 'Ordering already defined' if empty == true && !@ordering_builder.nil?
309
+ @ordering_builder ||= Ordering::OrderingParameterBuilder.instance
310
+ end
311
+
312
+ def arel_table
313
+ model_class.arel_table
314
+ end
315
+
316
+ def model_class_defined?
317
+ !@model_class.nil?
318
+ end
319
+
320
+ attr_reader :joins
321
+
322
+ def initialize(*args, **opts)
323
+ @joins = []
324
+ @ordering_builder = nil
325
+ super
326
+ end
327
+
328
+ def finish
329
+ raise ParamsReadyError, 'Ordering must be explicitly declared' if @ordering_builder&.open?
330
+ @ordering_builder = nil
331
+ super
332
+ end
333
+
334
+ parameter_class Relation
335
+ end
336
+ end
337
+ end