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