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,102 @@
1
+ require 'set'
2
+ require_relative 'struct_parameter'
3
+ require_relative 'value_parameter'
4
+ require_relative '../intent'
5
+ require_relative '../marshaller/enum_set_marshallers'
6
+ require_relative '../marshaller/parameter_module'
7
+
8
+ module ParamsReady
9
+ module Parameter
10
+ class EnumSetParameter < AbstractStructParameter
11
+ include Marshaller::ParameterModule
12
+
13
+ def self.intent_for_set(intent)
14
+ Intent.new(
15
+ intent.format.update(
16
+ omit: [],
17
+ remap: false
18
+ ),
19
+ intent.restriction
20
+ )
21
+ end
22
+
23
+ def member?(key)
24
+ raise ParamsReadyError, "Key not defined: '#{key}'" unless definition.has_child? key
25
+
26
+ if is_definite?
27
+ bare_value[key].unwrap == true
28
+ else
29
+ false
30
+ end
31
+ end
32
+ end
33
+
34
+ class EnumSetParameterBuilder < Builder
35
+ include Marshaller::BuilderModule
36
+
37
+ register :enum_set
38
+ register_deprecated :hash_set, use: :enum_set
39
+
40
+ def self.instance(name, altn: nil, type: :boolean)
41
+ new EnumSetParameterDefinition.new(name, altn: altn, type: type)
42
+ end
43
+
44
+ def add(input, *args, val: nil, **opts, &block)
45
+ type = @definition.type
46
+ definition = self.class.resolve(type, input, *args, **opts, &block)
47
+ @definition.add_child definition, value: val
48
+ end
49
+
50
+
51
+ def self.resolve(type, input, *args, **opts, &block)
52
+ if input.is_a? AbstractDefinition
53
+ input
54
+ else
55
+ define_registered_parameter(type, input, *args, **opts, &block)
56
+ end
57
+ end
58
+ end
59
+
60
+ class EnumSetParameterDefinition < AbstractStructParameterDefinition
61
+ attr_reader :type, :values
62
+ freeze_variable :values
63
+ name_for_formatter :enum_set
64
+ parameter_class EnumSetParameter
65
+ include Marshaller::DefinitionModule[Marshaller::EnumSetMarshallers.collection]
66
+
67
+ def initialize(*args, type: :boolean, **opts)
68
+ @type = type
69
+ @values = {}
70
+ super *args, **opts
71
+ end
72
+
73
+ def ensure_canonical(set)
74
+ raise ParamsReadyError, "Unexpected default type: #{set.class.name}" unless set.is_a?(Set)
75
+
76
+ context = Format.instance(:backend)
77
+ value, _validator = try_canonicalize set, context, nil, freeze: true
78
+ return value if value.length == set.length
79
+
80
+ extra_keys = set.reject do |key|
81
+ value.key?(key) || value.key?(key.to_s)
82
+ end.map do |key|
83
+ "'#{key.to_s}'"
84
+ end.join(", ")
85
+ raise ParamsReadyError, "extra elements found -- #{extra_keys}" if extra_keys.length > 0
86
+
87
+ value
88
+ end
89
+
90
+ def add_child(child, value:)
91
+ value = value.nil? ? child.name : value
92
+
93
+ if @values.key(value).nil?
94
+ @values[child.name] = value
95
+ else
96
+ raise ParamsReadyError, "Value '#{value}' already taken by '#{@values.key(value)}'"
97
+ end
98
+ super child
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,475 @@
1
+ require 'forwardable'
2
+ require_relative '../extensions/freezer'
3
+ require_relative '../error'
4
+ require_relative '../helpers/memo'
5
+ require_relative '../helpers/find_in_hash'
6
+
7
+ module ParamsReady
8
+ module Parameter
9
+ module FromHash
10
+ def set_from_hash(hash, context: nil, validator: Result.new(name))
11
+ if no_input?(context)
12
+ populate(context, validator)
13
+ else
14
+ _, input = find_in_hash hash, context
15
+ set_from_input(input, context, validator)
16
+ end
17
+ end
18
+ end
19
+
20
+ module ComplexParameter
21
+ def update_child(value, path)
22
+ child, child_name, child_path = child_for_update(path)
23
+ changed, updated = child.update_if_applicable(value, child_path)
24
+
25
+ if frozen? && !changed
26
+ [false, self]
27
+ else
28
+ clone = updated_clone(child_name, updated)
29
+ [true, clone]
30
+ end
31
+ end
32
+ end
33
+
34
+ module DelegatingParameter
35
+ include ComplexParameter
36
+ include FromHash
37
+
38
+ def self.included(recipient)
39
+ recipient.freeze_variable :data
40
+ end
41
+
42
+ def method_missing(name, *args)
43
+ if @data.respond_to?(name)
44
+ @data.send name, *args
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def respond_to_missing?(name, include_private = false)
51
+ if @data.respond_to?(name, include_private)
52
+ true
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def set_value(input, context = Format.instance(:backend), validator = nil)
59
+ if self.match?(input)
60
+ super input.unwrap, context, validator
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def hash
67
+ [definition, data].hash
68
+ end
69
+
70
+ def ==(other)
71
+ return false unless self.match?(other)
72
+ data == other.data
73
+ end
74
+
75
+ alias_method :eql?, :==
76
+
77
+ protected
78
+
79
+ def child_for_update(path)
80
+ [@data, nil, *path]
81
+ end
82
+
83
+ def updated_clone(_child_name, updated)
84
+ clone = definition.create
85
+ clone.instance_variable_set :@data, updated
86
+ clone.freeze if frozen?
87
+ clone
88
+ end
89
+
90
+ def set_from_input(input, context, validator)
91
+ if self.match?(input)
92
+ super input.unwrap, context, validator
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ def populate_other(other)
99
+ data.populate_other(other.data)
100
+ end
101
+ end
102
+
103
+ class AbstractParameter
104
+ attr_reader :definition
105
+ extend Forwardable
106
+ extend Extensions::Freezer
107
+ include Extensions::Freezer::InstanceMethods
108
+ include FromHash
109
+
110
+ def_delegators :@definition, :name, :altn, :name_for_formatter
111
+
112
+ def self.intent_for_children(method, &block)
113
+ case method
114
+ when :restriction
115
+ raise ParamsReadyError, "Block unexpected for '#{method}' method" unless block.nil?
116
+ define_method :intent_for_children do |intent|
117
+ intent.for_children(self)
118
+ end
119
+ when :delegate
120
+ define_method :intent_for_children do |intent|
121
+ delegate_name, *others = self.instance_eval(&block)
122
+ intent.delegate(self, delegate_name, *others)
123
+ end
124
+ when :pass
125
+ raise ParamsReadyError, "Block unexpected for '#{method}' method" unless block.nil?
126
+ define_method :intent_for_children do |intent|
127
+ intent
128
+ end
129
+ else
130
+ raise ParamsReadyError, "Unimplemented permission method: '#{method}'"
131
+ end
132
+ end
133
+
134
+ intent_for_children :pass
135
+
136
+ def initialize(definition, **options)
137
+ raise ParamsReadyError, "Unexpected options: #{options}" unless options.empty?
138
+ @definition = definition
139
+ end
140
+
141
+ def update_in(value, path)
142
+ _, updated = update_if_applicable(value, path)
143
+ updated
144
+ end
145
+
146
+ def update_if_applicable(value, path)
147
+ if path.empty?
148
+ update_self(value)
149
+ elsif respond_to? :update_child
150
+ update_child(value, path)
151
+ else
152
+ raise ParamsReadyError, "Expected path to be terminated in '#{name}'"
153
+ end
154
+ end
155
+
156
+ def populate(context, validator)
157
+ return if definition.populator.nil?
158
+
159
+ definition.populator.call(context, self)
160
+ validator
161
+ rescue => error
162
+ populator_error = PopulatorError.new(error)
163
+ if validator.nil?
164
+ raise populator_error
165
+ else
166
+ validator.error! populator_error
167
+ end
168
+ validator
169
+ end
170
+
171
+ def match?(other)
172
+ return false unless other.instance_of?(self.class)
173
+ definition == other.definition
174
+ end
175
+
176
+ def ==(other)
177
+ return false unless self.match?(other)
178
+
179
+ bare_value == other.bare_value
180
+ rescue
181
+ false
182
+ end
183
+
184
+ def to_hash(format = Format.instance(:backend), restriction: nil, data: nil)
185
+ restriction ||= Restriction.blanket_permission
186
+ intent = Intent.new(format, restriction, data: data)
187
+ to_hash_if_eligible(intent) || {}
188
+ end
189
+
190
+ def inspect
191
+ preserve = Format.instance(:inspect).preserve?(self)
192
+ content = preserve ? inspect_content : '[FILTERED]'
193
+ "#{self.class.name.split("::").last} #{self.name}: { #{content} }"
194
+ end
195
+
196
+ def dup
197
+ clone = definition.create
198
+ populate_other clone
199
+ clone
200
+ end
201
+
202
+ protected
203
+
204
+ def update_self(value)
205
+ clone = definition.create
206
+ clone.set_value value
207
+ clone.freeze if frozen?
208
+ [true, clone]
209
+ end
210
+ end
211
+
212
+ class Parameter < AbstractParameter
213
+ def_delegators :@definition,
214
+ :default, :optional?, :default_defined?, :constraints, :no_output?, :no_input?
215
+
216
+ def initialize(definition)
217
+ @value = Extensions::Undefined
218
+ super definition
219
+ end
220
+
221
+ def set_value(input, context = Format.instance(:backend), validator = nil)
222
+ if Extensions::Undefined.value_indefinite?(input)
223
+ handle_indefinite_input(input, validator)
224
+ elsif self.match? input
225
+ @value = input.bare_value
226
+ else
227
+ begin
228
+ value, validator = definition.try_canonicalize(input, context, validator)
229
+ if validator.nil? || validator.ok?
230
+ if Extensions::Undefined.value_indefinite?(value)
231
+ handle_indefinite_input(value, validator)
232
+ else
233
+ @value = value
234
+ end
235
+ end
236
+ rescue StandardError => e
237
+ if validator.nil?
238
+ raise e
239
+ else
240
+ validator.error! e
241
+ end
242
+ end
243
+ end
244
+ validator
245
+ end
246
+
247
+ def definite_default?
248
+ default_defined? && !default.nil?
249
+ end
250
+
251
+ def nil_default?
252
+ default_defined? && default.nil?
253
+ end
254
+
255
+ def is_definite?
256
+ return true if @value != Extensions::Undefined && !@value.nil?
257
+ return false if optional? && @value.nil?
258
+
259
+ definite_default?
260
+ end
261
+
262
+ def is_default?
263
+ return false unless default_defined?
264
+
265
+ @value == Extensions::Undefined || @value == default
266
+ end
267
+
268
+ def is_nil?
269
+ return false if is_definite?
270
+ return true if optional?
271
+ return true if nil_default?
272
+
273
+ false
274
+ end
275
+
276
+ def is_undefined?
277
+ @value == Extensions::Undefined && allows_undefined?
278
+ end
279
+
280
+ def allows_undefined?
281
+ return true if optional?
282
+
283
+ !default_defined?
284
+ end
285
+
286
+ def eligible_for_output?(intent)
287
+ intent.preserve?(self)
288
+ end
289
+
290
+ def hash_key(format)
291
+ format.hash_key(self)
292
+ end
293
+
294
+ def set_from_input(input, context, validator)
295
+ preprocessed = definition.preprocess(input, context, validator)
296
+ set_value preprocessed, context, validator
297
+ definition.postprocess(self, context, validator)
298
+ validator
299
+ end
300
+
301
+ def to_hash_if_eligible(intent = Intent.instance(:backend))
302
+ return nil unless eligible_for_output? intent
303
+
304
+ formatted = format_self_permitted(intent)
305
+ wrap_output(formatted, intent)
306
+ end
307
+
308
+ def format_self_permitted(intent)
309
+ intent = intent_for_children(intent)
310
+ format(intent)
311
+ end
312
+
313
+ def format(intent)
314
+ value = memo(intent)
315
+ return value if value != Extensions::Undefined
316
+
317
+ value = marshal(intent)
318
+ memo!(value, intent)
319
+ value
320
+ end
321
+
322
+ def memo(intent)
323
+ return Extensions::Undefined if @memo.nil?
324
+
325
+ @memo.cached_value(intent)
326
+ end
327
+
328
+ def memo!(value, intent)
329
+ return if @memo.nil? || !frozen?
330
+
331
+ @memo.cache_value(value, intent)
332
+ end
333
+
334
+ def wrap_output(output, intent)
335
+ name_or_path = hash_key(intent)
336
+ if name_or_path.is_a? Array
337
+ *path, name = name_or_path
338
+ result = {}
339
+ Helpers::KeyMap::Mapping::Path.store(name, output, result, path)
340
+ result
341
+ else
342
+ { name_or_path => output }
343
+ end
344
+ end
345
+
346
+ def unwrap
347
+ format(Intent.instance(:backend))
348
+ end
349
+
350
+ def unwrap_or(*args, &block)
351
+ ensure_default_present!(*args, &block)
352
+
353
+ if is_definite?
354
+ begin
355
+ unwrap
356
+ rescue StandardError => _
357
+ supply_default(*args, &block)
358
+ end
359
+ else
360
+ supply_default(*args, &block)
361
+ end
362
+ end
363
+
364
+ def find_in_hash(hash, context)
365
+ Helpers::FindInHash.find_in_hash hash, hash_key(context)
366
+ end
367
+
368
+ def populate_other(other)
369
+ raise ParamsReadyError, "Not a matching param: #{other.class.name}" unless match? other
370
+ return other unless is_definite?
371
+
372
+ value = bare_value
373
+ other.populate_with(value)
374
+ other
375
+ end
376
+
377
+ def inspect_content
378
+ @value.inspect
379
+ end
380
+
381
+ freeze_variable :value
382
+
383
+ def freeze
384
+ if definition.memoize? and !frozen?
385
+ @memo = Helpers::Memo.new(definition.memoize)
386
+ end
387
+ init_for_read true
388
+ super
389
+ end
390
+
391
+ def hash
392
+ [definition, @value].hash
393
+ end
394
+
395
+ alias_method :eql?, :==
396
+
397
+ protected
398
+
399
+ def handle_indefinite_input(input, validator)
400
+ value_missing validator
401
+ if default_defined?
402
+ # if value_missing doesn't crash,
403
+ # and the parameter is optional
404
+ # it's safe to set to nil or Extensions::Undefined
405
+ @value = Extensions::Undefined
406
+ elsif optional?
407
+ # Clear possible previous state,
408
+ # will be set to default on read
409
+ @value = input
410
+ else
411
+ raise ParamsReadyError, "Unexpected state for '#{name}' in #handle_indefinite_input" if validator.ok?
412
+ end
413
+ end
414
+
415
+ def value_missing(validator = nil)
416
+ if !nil_allowed?
417
+ e = ValueMissingError.new self.name
418
+ if validator
419
+ validator.error!(e)
420
+ else
421
+ raise e
422
+ end
423
+ end
424
+ validator
425
+ end
426
+
427
+ def nil_allowed?
428
+ optional? || default_defined?
429
+ end
430
+
431
+ def bare_value
432
+ init_for_read
433
+ return @value if is_definite?
434
+
435
+ value_missing
436
+ nil
437
+ end
438
+
439
+ def ensure_default_present!(*args, &block)
440
+ raise ParamsReadyError, 'Single default value expected' if args.length > 1
441
+ raise ParamsReadyError, 'Supply either default or a block' if args.length == 0 && block.nil?
442
+ warn 'WARNING: block supersedes default value' if args.length > 0 && block
443
+ end
444
+
445
+ def supply_default(*args, &block)
446
+ if block.nil?
447
+ args[0]
448
+ else
449
+ block.call
450
+ end
451
+ end
452
+
453
+ def init_for_read(to_be_frozen = false)
454
+ return unless @value == Extensions::Undefined
455
+ return unless default_defined?
456
+
457
+ @value = definition.fetch_default(duplicate: !to_be_frozen)
458
+ end
459
+
460
+ def init_for_write
461
+ return if is_definite?
462
+
463
+ if default_defined? && !default.nil?
464
+ @value = definition.fetch_default
465
+ else
466
+ init_value
467
+ end
468
+ end
469
+
470
+ def init_value
471
+ # NOOP
472
+ end
473
+ end
474
+ end
475
+ end