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,254 @@
1
+ require_relative '../parameter/struct_parameter'
2
+ require_relative '../value/constraint'
3
+ require_relative '../helpers/arel_builder'
4
+ require_relative 'abstract_pagination'
5
+ require_relative 'direction'
6
+
7
+ module ParamsReady
8
+ module Pagination
9
+ class KeysetPagination < Parameter::StructParameter
10
+ include AbstractPagination
11
+
12
+ def select_keysets(query, limit, direction, keyset, ordering, arel_table, context)
13
+ query = keyset_query(query, limit, direction, keyset, ordering, arel_table, context)
14
+ query.project(*cursor_columns_arel(ordering, arel_table, context))
15
+ end
16
+
17
+ def keyset_query(query, limit, direction, keyset, ordering, arel_table, context)
18
+ cursor, grouping = cursor_predicates(direction, keyset, ordering, arel_table, context)
19
+ cte = cursor.cte_for_query(query, arel_table) unless cursor.nil?
20
+ query = query.where(grouping) unless grouping.nil?
21
+
22
+ query = query.with(cte) unless cte.nil?
23
+ ordered = query.order(ordering_arel(direction, ordering, arel_table, context))
24
+ ordered.take(limit)
25
+ end
26
+
27
+ def keysets_for_relation(relation, limit, direction, keyset, ordering, context)
28
+ arel_table = relation.arel_table
29
+
30
+ cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)
31
+ full_query = relation.where(predicates)
32
+ .reorder(ordering_arel(direction, ordering, arel_table, context))
33
+ .limit(limit)
34
+ .select(*cursor_columns_arel(ordering, arel_table, context))
35
+ full_query = Arel::Nodes::SqlLiteral.new(full_query.to_sql)
36
+ with_cte(relation, full_query, cursor)
37
+ end
38
+
39
+ def paginate_relation(relation, ordering, context)
40
+ arel_table = relation.arel_table
41
+ cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)
42
+
43
+ subselect = relation.where(predicates)
44
+ .reorder(ordering_arel(direction, ordering, arel_table, context))
45
+ .limit(limit)
46
+ .select(primary_keys_arel(ordering, arel_table, context))
47
+
48
+ subselect_sql = Arel::Nodes::SqlLiteral.new(subselect.to_sql)
49
+ subselect_sql = with_cte_grouped(relation, subselect_sql, cursor)
50
+
51
+ exists = exists_predicate(subselect_sql, ordering, arel_table)
52
+ relation.where(exists)
53
+ end
54
+
55
+ def paginate_query(query, ordering, arel_table, context)
56
+ cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)
57
+ cte = cursor.cte_for_query(query, arel_table) unless cursor.nil?
58
+ subquery = query.deep_dup
59
+ subquery = subquery.where(predicates) unless predicates.nil?
60
+ subquery = subquery.with(cte) unless cte.nil?
61
+
62
+ subselect = subquery.order(ordering_arel(direction, ordering, arel_table, context))
63
+ .take(limit)
64
+ .project(primary_keys_arel(ordering, arel_table, context))
65
+
66
+ exists = exists_predicate(subselect, ordering, arel_table)
67
+ query.where(exists)
68
+ end
69
+
70
+ def exists_predicate(subselect, ordering, arel_table)
71
+ table_alias = self.table_alias(arel_table)
72
+ aliased = arel_table.alias(table_alias)
73
+ select_manager = Arel::SelectManager.new.from(subselect.as(table_alias))
74
+ related = related_clause(arel_table, aliased, ordering.definition.primary_keys)
75
+ select_manager.where(related).project('1').exists
76
+ end
77
+
78
+ def related_clause(arel_table, aliased, primary_keys)
79
+ cursor_columns.reduce(nil) do |clause, name|
80
+ next clause unless primary_keys.member?(name)
81
+
82
+ predicate = arel_table[name].eq(aliased[name])
83
+ next predicate if clause.nil?
84
+
85
+ next clause.and(predicate)
86
+ end
87
+ end
88
+
89
+ def with_cte_grouped(relation, select, cursor)
90
+ with_cte = with_cte(relation, select, cursor)
91
+ Arel::Nodes::Grouping.new(with_cte)
92
+ end
93
+
94
+ def with_cte(relation, select, cursor)
95
+ return select if cursor.nil?
96
+
97
+ cte = cursor.cte_for_relation(relation)
98
+ return select if cte.nil?
99
+
100
+ Arel::Nodes::SqlLiteral.new([cte.to_sql, select].join(' '))
101
+ end
102
+
103
+ def table_alias(arel_table)
104
+ Helpers::ArelBuilder.safe_name "#{arel_table.name}_#{cursor_columns.join('_')}"
105
+ end
106
+
107
+ def ordering_arel(direction, ordering, arel_table, context)
108
+ inverted = Direction.instance(direction).invert_ordering?
109
+ ordering.to_arel(arel_table, context: context, inverted: inverted)
110
+ end
111
+
112
+ def cursor_predicates(direction, keyset, ordering, arel_table, context)
113
+ direction = Direction.instance(direction)
114
+ direction.cursor_predicates(keyset, ordering, arel_table, context)
115
+ end
116
+
117
+ def first_page_value
118
+ { limit: limit, direction: :aft, keyset: {} }
119
+ end
120
+
121
+ def last_page_value
122
+ { limit: limit, direction: :bfr, keyset: {} }
123
+ end
124
+
125
+ def before_page_value(keyset)
126
+ keyset ||= {}
127
+ { limit: limit, direction: :bfr, keyset: keyset }
128
+ end
129
+
130
+ def after_page_value(keyset)
131
+ keyset ||= {}
132
+ { limit: limit, direction: :aft, keyset: keyset }
133
+ end
134
+
135
+ def limit
136
+ self[:limit].unwrap
137
+ end
138
+
139
+ def limit_key
140
+ :limit
141
+ end
142
+
143
+ def direction
144
+ self[:direction].unwrap
145
+ end
146
+
147
+ def keyset
148
+ self[:keyset].unwrap
149
+ end
150
+
151
+ def cursor_columns
152
+ self[:keyset].names.keys
153
+ end
154
+
155
+ def cursor_columns_arel(ordering, arel_table, context, columns: cursor_columns)
156
+ columns.map do |name|
157
+ column = ordering.definition.columns[name]
158
+ column.attribute(name, arel_table, context)
159
+ end
160
+ end
161
+
162
+ def primary_keys_arel(ordering, arel_table, context)
163
+ columns = cursor_columns.lazy.select do |name|
164
+ ordering.definition.primary_keys.member? name
165
+ end
166
+
167
+ cursor_columns_arel(ordering, arel_table, context, columns: columns).force
168
+ end
169
+
170
+ def cursor
171
+ return nil unless is_definite?
172
+
173
+ keyset = self[:keyset]
174
+ keyset.names.keys.map do |column_name|
175
+ keyset[column_name].unwrap
176
+ end
177
+ end
178
+ end
179
+
180
+ class KeysetPaginationDefinition < Parameter::StructParameterDefinition
181
+ MIN_LIMIT = 1
182
+
183
+ parameter_class KeysetPagination
184
+
185
+ attr_reader :default_limit
186
+
187
+ def initialize(default_limit, max_limit = nil)
188
+ super :pagination,
189
+ altn: :pgn
190
+
191
+ @default_limit = default_limit
192
+
193
+ direction = Builder.define_symbol(:direction, altn: :dir) do
194
+ constrain :enum, [:bfr, :aft]
195
+ end
196
+
197
+ limit = Builder.define_integer(:limit, altn: :lmt) do
198
+ constrain Value::OperatorConstraint.new(:>=, MIN_LIMIT), strategy: :clamp
199
+ constrain Value::OperatorConstraint.new(:<=, max_limit), strategy: :clamp unless max_limit.nil?
200
+ end
201
+ add_child(direction)
202
+ add_child(limit)
203
+ end
204
+
205
+ def finish
206
+ keyset = names[:keyset]
207
+ raise ParamsReadyError, "No cursor defined" if keyset.nil? || keyset.names.length < 1
208
+ super
209
+ end
210
+ end
211
+
212
+ class KeysetPaginationBuilder
213
+ def initialize(ordering_builder, default_limit, max_limit = nil)
214
+ definition = KeysetPaginationDefinition.new(default_limit, max_limit)
215
+ @cursor_builder = Parameter::StructParameterBuilder.send :new, definition
216
+ @default = {
217
+ limit: default_limit,
218
+ direction: :aft,
219
+ keyset: {}
220
+ }
221
+ @ordering_builder = ordering_builder
222
+ @keyset = Parameter::StructParameterBuilder.instance(:keyset, altn: :ks)
223
+ end
224
+
225
+ def key(type, name, direction, &block)
226
+ add_to_cursor(type, name, &block)
227
+ @ordering_builder.column name, direction, required: true, pk: true
228
+ end
229
+
230
+ def column(type, name, direction, **opts, &block)
231
+ add_to_cursor(type, name, &block)
232
+ @ordering_builder.column name, direction, **opts
233
+ end
234
+
235
+ def base64
236
+ @cursor_builder.marshal using: :base64
237
+ end
238
+
239
+ def add_to_cursor(type, name, &block)
240
+ @keyset.add type, name do
241
+ optional
242
+ include(&block) unless block.nil?
243
+ end
244
+ end
245
+
246
+ def build(&block)
247
+ instance_eval(&block)
248
+ @cursor_builder.add @keyset.build
249
+ @cursor_builder.default @default
250
+ @cursor_builder.build
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,70 @@
1
+ module ParamsReady
2
+ module Pagination
3
+ class AbstractKeysets
4
+ attr_reader :keysets
5
+
6
+ def initialize(keysets, &block)
7
+ @keysets = keysets
8
+ @transform = block
9
+ end
10
+
11
+ def length
12
+ @keysets.length
13
+ end
14
+
15
+ def transform(raw)
16
+ return raw if @transform.nil?
17
+ @transform.call(raw)
18
+ end
19
+ end
20
+
21
+ class BeforeKeysets < AbstractKeysets
22
+ def page(delta, limit)
23
+ raise "Expected positive integer for limit, got: #{limit}" if limit < 1
24
+ raise "Expected non-negative integer for delta, got: #{delta}" if delta < 0
25
+
26
+ if delta == 0
27
+ transform(@keysets.first)
28
+ else
29
+ shift = delta * limit
30
+ diff = @keysets.length - shift
31
+ if diff > 0
32
+ transform(@keysets[shift])
33
+ elsif diff.abs < limit
34
+ {}
35
+ else
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ class AfterKeysets < AbstractKeysets
43
+ attr_reader :last
44
+
45
+ def initialize(last, keysets, &block)
46
+ @last = last
47
+ super keysets, &block
48
+ end
49
+
50
+ def page(delta, limit)
51
+ raise "Expected positive integer for limit, got: #{limit}" if limit < 1
52
+ raise "Expected positive integer for delta, got: #{delta}" if delta < 1
53
+ return if @keysets.length.zero?
54
+
55
+ shift = (delta - 1) * limit
56
+
57
+ if shift == 0
58
+ @last
59
+ else
60
+ diff = @keysets.length - shift
61
+ if diff < 1
62
+ nil
63
+ else
64
+ transform(@keysets[shift - 1])
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../error'
2
+ require_relative 'tendency'
3
+
4
+ module ParamsReady
5
+ module Pagination
6
+ module Nulls
7
+ module First
8
+ def self.if_null_predicate(column, nested)
9
+ is_null_and_all = column.eq(nil).and(nested)
10
+ grouping = Arel::Nodes::Grouping.new(is_null_and_all)
11
+ is_not_null = column.not_eq(nil)
12
+ grouping.or(is_not_null)
13
+ end
14
+
15
+ def self.if_not_null_predicate(tendency, column, value, nested)
16
+ tendency.non_nullable_predicate(column, value, nested)
17
+ end
18
+ end
19
+
20
+ module Last
21
+ def self.if_null_predicate(column, nested)
22
+ Arel::Nodes::Grouping.new(column.eq(nil).and(nested))
23
+ end
24
+
25
+ def self.if_not_null_predicate(tendency, column, value, nested)
26
+ tendency.non_nullable_predicate(column, value, nested).or(column.eq(nil))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,130 @@
1
+ require_relative '../parameter/tuple_parameter'
2
+ require_relative '../value/validator'
3
+ require_relative 'abstract_pagination'
4
+
5
+ module ParamsReady
6
+ module Pagination
7
+ class OffsetPagination < Parameter::TupleParameter
8
+ include AbstractPagination
9
+
10
+ def paginate_relation(relation, _, _)
11
+ relation.offset(offset).limit(limit)
12
+ end
13
+
14
+ def paginate_query(query, _, _, _)
15
+ query.skip(offset).take(limit)
16
+ end
17
+
18
+ def offset=(off)
19
+ self.first.set_value off
20
+ end
21
+
22
+ def offset
23
+ first.unwrap
24
+ end
25
+
26
+ def limit=(lmt)
27
+ self.second.set_value lmt
28
+ end
29
+
30
+ def limit
31
+ second.unwrap
32
+ end
33
+
34
+ def limit_key
35
+ 1
36
+ end
37
+
38
+ def page_no
39
+ ((offset + limit - 1) / limit) + 1
40
+ end
41
+
42
+ def page_value(delta, count: nil)
43
+ return nil unless can_yield_page?(delta, count: count)
44
+
45
+ [new_offset(delta), limit]
46
+ end
47
+
48
+ def current_page_value
49
+ page_value(0)
50
+ end
51
+
52
+ def previous_page_value(delta = 1)
53
+ page_value(-delta)
54
+ end
55
+
56
+ def next_page_value(delta = 1, count: nil)
57
+ page_value(delta, count: count)
58
+ end
59
+
60
+ def first_page_value
61
+ [0, limit]
62
+ end
63
+
64
+ def last_page_value(count:)
65
+ num_pages = num_pages(count: count)
66
+ return nil if num_pages == 0
67
+
68
+ new_offset = (num_pages - 1) * limit
69
+ [new_offset, limit]
70
+ end
71
+
72
+ def new_offset(delta)
73
+ shift = delta * limit
74
+ no = offset + shift
75
+ return no if no >= 0
76
+ return 0 if shift.abs < offset + limit
77
+
78
+ nil
79
+ end
80
+
81
+ def has_previous?(delta = 1)
82
+ raise ParamsReadyError, 'Negative delta unexpected' if delta < 0
83
+ return false if offset == 0
84
+
85
+ delta * limit < offset + limit
86
+ end
87
+
88
+ def has_next?(delta = 1, count:)
89
+ raise ParamsReadyError, 'Nil count unexpected' if count.nil?
90
+ raise ParamsReadyError, 'Negative delta unexpected' if delta < 0
91
+
92
+ offset + (delta * limit) < count
93
+ end
94
+
95
+ def has_page?(delta, count: nil)
96
+ if delta > 0
97
+ has_next? delta, count: count
98
+ else
99
+ has_previous? -delta
100
+ end
101
+ end
102
+
103
+ def can_yield_page?(delta, count: nil)
104
+ return true if delta >= 0 && count.nil?
105
+
106
+ has_page?(delta, count: count)
107
+ end
108
+ end
109
+
110
+ class OffsetPaginationDefinition < Parameter::TupleParameterDefinition
111
+ MIN_LIMIT = 1
112
+ parameter_class OffsetPagination
113
+
114
+ def initialize(default_offset, default_limit, max_limit = nil)
115
+ offset = Builder.define_integer(:offset, altn: :off) do
116
+ constrain Value::OperatorConstraint.new(:>=, 0), strategy: :clamp
117
+ end
118
+ limit = Builder.define_integer(:limit, altn: :lmt) do
119
+ constrain Value::OperatorConstraint.new(:>=, MIN_LIMIT), strategy: :clamp
120
+ constrain Value::OperatorConstraint.new(:<=, max_limit), strategy: :clamp unless max_limit.nil?
121
+ end
122
+ super :pagination,
123
+ altn: :pgn,
124
+ marshaller: { using: :string, separator: '-' },
125
+ fields: [offset, limit],
126
+ default: [default_offset, default_limit]
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,28 @@
1
+ module ParamsReady
2
+ module Pagination
3
+ module Tendency
4
+ def non_nullable_predicate(column, value, nested)
5
+ if_equal = column.eq(value).and(nested)
6
+ grouping = Arel::Nodes::Grouping.new(if_equal)
7
+ comparison = comparison_predicate(column, value)
8
+ grouping.or(comparison)
9
+ end
10
+
11
+ module Growing
12
+ extend Tendency
13
+
14
+ def self.comparison_predicate(column, value)
15
+ column.gt(value)
16
+ end
17
+ end
18
+
19
+ module Falling
20
+ extend Tendency
21
+
22
+ def self.comparison_predicate(column, value)
23
+ column.lt(value)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end