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,76 @@
1
+ require 'set'
2
+
3
+ module ParamsReady
4
+ module Helpers
5
+ def self.Rule(input)
6
+ return input if input.nil?
7
+ return input if input.is_a? Rule
8
+
9
+ Rule.instance(input).freeze
10
+ end
11
+
12
+ class Rule
13
+ attr_reader :hash, :mode, :values
14
+
15
+ def self.instance(input)
16
+ mode, values = case input
17
+ when :none, :all then [input, nil]
18
+ when Hash
19
+ if input.length > 1 || input.length < 1
20
+ raise ParamsReadyError, "Unexpected hash for rule: '#{input}'"
21
+ end
22
+ key, values = input.first
23
+ case key
24
+ when :except, :only then [key, values.to_set.freeze]
25
+ else
26
+ raise ParamsReadyError, "Unexpected mode for rule: '#{key}'"
27
+ end
28
+ else
29
+ raise ParamsReadyError, "Unexpected input for rule: '#{input}'"
30
+ end
31
+ new(mode, values)
32
+ end
33
+
34
+ def initialize(mode, values)
35
+ @mode = mode
36
+ @values = values.freeze
37
+ @hash = [@mode, @values].hash
38
+ freeze
39
+ end
40
+
41
+ def merge(other)
42
+ return self if other.nil?
43
+ raise ParamsReadyError, "Can't merge with #{other.class.name}" unless other.is_a? Rule
44
+ raise ParamsReadyError, "Can't merge incompatible rules: #{mode}/#{other.mode}" if other.mode != mode
45
+
46
+ case mode
47
+ when :all, :none
48
+ self
49
+ when :only, :except
50
+ values = self.values + other.values
51
+ Rule.new(mode, values)
52
+ end
53
+ end
54
+
55
+ def include?(name)
56
+ case @mode
57
+ when :none then false
58
+ when :all then true
59
+ when :only then @values.member? name
60
+ when :except
61
+ !@values.member? name
62
+ else
63
+ raise ParamsReadyError, "Unexpected mode for rule: '#{@mode}'"
64
+ end
65
+ end
66
+
67
+ def ==(other)
68
+ return false unless other.is_a? Rule
69
+ return true if object_id == other.object_id
70
+ return false unless @mode == other.instance_variable_get(:@mode)
71
+
72
+ @values == other.instance_variable_get(:@values)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ module ParamsReady
2
+ module Helpers
3
+ class Storage
4
+ attr_reader :parameters, :relations
5
+
6
+ def initialize
7
+ @parameters = Hash.new
8
+ @relations = Hash.new
9
+ end
10
+
11
+ def has_relation?(name)
12
+ relations.key? name
13
+ end
14
+
15
+ def has_parameter?(name)
16
+ parameters.key? name
17
+ end
18
+
19
+ def add_relation(relation)
20
+ raise ParamsReadyError, "Relation already exists: #{relation.name}" if self.has_relation?(relation.name)
21
+ @relations[relation.name] = relation
22
+ end
23
+
24
+ def add_parameter(param)
25
+ raise ParamsReadyError, "Parameter already exists: #{param.name}" if self.has_parameter?(param.name)
26
+ @parameters[param.name] = param
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'rule'
2
+
3
+ module ParamsReady
4
+ module Helpers
5
+ class UsageRule
6
+ attr_reader :parameter_definition, :rule
7
+
8
+ def initialize(parameter_definition, rule = :all)
9
+ @parameter_definition = parameter_definition
10
+ @rule = ParamsReady::Helpers::Rule(rule)
11
+ freeze
12
+ end
13
+
14
+ def valid_for?(method)
15
+ @rule.include? method
16
+ end
17
+
18
+ def name
19
+ parameter_definition.name
20
+ end
21
+
22
+ def merge(other)
23
+ return self if other.nil?
24
+ raise ParamsReadyError, "Can't merge into #{other.class.name}" unless other.is_a? UsageRule
25
+
26
+ unless parameter_definition == other.parameter_definition
27
+ message = "Can't merge incompatible rules: #{parameter_definition.name}/#{other.parameter_definition.name}"
28
+ raise ParamsReadyError, message
29
+ end
30
+
31
+ rule = self.rule.merge(other.rule)
32
+ UsageRule.new(parameter_definition, rule)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ require 'forwardable'
2
+ require_relative 'format'
3
+
4
+ module ParamsReady
5
+ class InputContext
6
+ include Format::Wrapper
7
+ extend Forwardable
8
+
9
+ attr_reader :data
10
+
11
+ def_delegator :data, :[]
12
+
13
+ def initialize(format, data = {})
14
+ @format = Format.resolve(format).freeze
15
+ @data = data.freeze
16
+ end
17
+
18
+ def self.resolve(unknown)
19
+ case unknown
20
+ when nil
21
+ Format.instance(:frontend)
22
+ when InputContext, Format
23
+ unknown
24
+ when Symbol
25
+ Format.instance(unknown)
26
+ else
27
+ raise ParamsReadyError, "Unexpected type for InputContext: #{unknown.class.name}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,70 @@
1
+ require 'forwardable'
2
+ require_relative 'restriction'
3
+ require_relative 'format'
4
+ require_relative 'parameter/parameter'
5
+
6
+ module ParamsReady
7
+ class Intent
8
+ extend Forwardable
9
+ include Restriction::Wrapper
10
+ include Format::Wrapper
11
+
12
+ def clone(restriction:)
13
+ Intent.new @format, restriction, data: @data
14
+ end
15
+
16
+ attr_reader :data, :hash
17
+
18
+ def initialize(format, restriction = Restriction.blanket_permission, data: nil)
19
+ @format = Format.resolve(format).freeze
20
+ raise ParamsReadyError, "Restriction expected, got: #{restriction.inspect}" unless restriction.is_a? Restriction
21
+ @restriction = restriction
22
+ @data = check_data(data)
23
+ @hash = [@format, @restriction, @data].hash
24
+ freeze
25
+ end
26
+
27
+ def check_data(data)
28
+ return if data.nil?
29
+ # The reason we require data object to be
30
+ # a Parameter is that it must be deep frozen
31
+ # for the output memoizing feature to work properly.
32
+ raise 'Data object must be a parameter' unless data.is_a? Parameter::Parameter
33
+ raise 'Data object must be frozen' unless data.frozen?
34
+
35
+ data
36
+ end
37
+
38
+ def omit?(parameter)
39
+ return true unless permitted?(parameter)
40
+ @format.omit?(parameter)
41
+ end
42
+
43
+ def preserve?(parameter)
44
+ !omit?(parameter)
45
+ end
46
+
47
+ def self.instance(name)
48
+ format = Format.instance(name)
49
+ Intent.new(format)
50
+ end
51
+
52
+ def self.resolve(intent_or_name)
53
+ if intent_or_name.is_a? Intent
54
+ intent_or_name
55
+ else
56
+ instance(intent_or_name)
57
+ end
58
+ end
59
+
60
+ def ==(other)
61
+ return false unless other.is_a?(Intent)
62
+ return true if object_id == other.object_id
63
+ restriction == other.restriction && format == other.format && data == other.data
64
+ end
65
+
66
+ def eql?(other)
67
+ self == other
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,132 @@
1
+ require_relative 'collection'
2
+ require_relative '../extensions/undefined'
3
+ require_relative '../extensions/hash'
4
+ require_relative '../helpers/find_in_hash'
5
+
6
+ module ParamsReady
7
+ module Marshaller
8
+ class ArrayMarshallers
9
+ module AbstractMarshaller
10
+ def marshal(parameter, intent)
11
+ array = parameter.send(:bare_value)
12
+ definition = parameter.definition
13
+ compact = definition.compact?
14
+
15
+ elements = array.map do |element|
16
+ if element.eligible_for_output?(intent)
17
+ element.format_self_permitted(intent)
18
+ end
19
+ end
20
+ elements = elements.compact if compact
21
+ do_marshal(elements, intent, compact)
22
+ end
23
+ end
24
+
25
+ module ArrayMarshaller
26
+ extend AbstractMarshaller
27
+
28
+ def self.canonicalize(definition, array, context, validator, freeze: false)
29
+ canonical = array.map do |value|
30
+ next if definition.compact? && value.nil?
31
+
32
+ element = definition.prototype.create
33
+ element.set_from_input(value, context, validator)
34
+ next if definition.compact? && element.is_nil?
35
+
36
+ element.freeze if freeze
37
+ element
38
+ end.compact
39
+
40
+ [canonical, validator]
41
+ end
42
+
43
+ def self.do_marshal(array, _, _)
44
+ array
45
+ end
46
+
47
+ freeze
48
+ end
49
+
50
+ module StructMarshaller
51
+ extend AbstractMarshaller
52
+
53
+ def self.canonicalize(definition, hash, context, validator)
54
+ if definition.compact?
55
+ ArrayMarshaller.canonicalize(definition, hash.values, context, validator)
56
+ else
57
+ count_key = :cnt
58
+ found, count = Helpers::FindInHash.find_in_hash hash, count_key
59
+ raise ParamsReadyError, "Count not found" unless found
60
+
61
+ count = Integer(count)
62
+ array = (0...count).map do |index|
63
+ found, value = Helpers::FindInHash.find_in_hash hash, index
64
+ element = definition.prototype.create
65
+ element.set_from_input(value, context, validator) if found
66
+ element
67
+ end
68
+ [array, validator]
69
+ end
70
+ end
71
+
72
+ def self.do_marshal(array, _, compact)
73
+ return array if compact
74
+
75
+ result = array.each_with_index.reduce({}) do |result, (element, index)|
76
+ index = index.to_s
77
+ result[index] = element
78
+ result
79
+ end
80
+
81
+ result['cnt'] = array.length.to_s
82
+ result
83
+ end
84
+
85
+ freeze
86
+ end
87
+
88
+ class StringMarshaller
89
+ include AbstractMarshaller
90
+
91
+ attr_reader :separator
92
+
93
+ def self.instance(separator:, split_pattern: nil)
94
+ instance = new separator, split_pattern
95
+ [String, instance.freeze]
96
+ end
97
+
98
+ def initialize(separator, split_pattern)
99
+ @separator = separator.to_s.freeze
100
+ @split_pattern = split_pattern.freeze
101
+ end
102
+
103
+ def split_pattern
104
+ @split_pattern || @separator
105
+ end
106
+
107
+ def canonicalize(definition, string, context, validator)
108
+ array = string.split(split_pattern).map(&:strip).reject(&:empty?)
109
+ ArrayMarshaller.canonicalize(definition, array, context, validator)
110
+ end
111
+
112
+ def do_marshal(array, _, _)
113
+ array.join(separator)
114
+ end
115
+
116
+ freeze
117
+ end
118
+
119
+ def self.collection
120
+ @collection ||= begin
121
+ c = ClassCollection.new Array
122
+ c.add_instance Array, ArrayMarshaller
123
+ c.add_instance Hash, StructMarshaller
124
+ c.add_factory :string, StringMarshaller
125
+ c.default!(Hash)
126
+ c.freeze
127
+ c
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,9 @@
1
+ module ParamsReady
2
+ module Marshaller
3
+ module BuilderModule
4
+ def marshal(to: nil, using: nil, **opts)
5
+ @definition.set_marshaller(to: to, using: using, **opts)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,165 @@
1
+ require_relative '../error'
2
+ require_relative '../extensions/hash'
3
+
4
+ module ParamsReady
5
+ module Marshaller
6
+ class InstanceCollection
7
+ attr_reader :default, :instances
8
+
9
+ def initialize(canonical, default = nil, instances = {})
10
+ @canonical = canonical
11
+ @default = default
12
+ @instances = instances
13
+ end
14
+
15
+ def canonicalize(definition, input, context, validator, **opts)
16
+ value_class = infer_class(input)
17
+ marshaller = instance(value_class)
18
+ raise ParamsReadyError, "Unexpected type for #{definition.name}: #{value_class.name}" if marshaller.nil?
19
+
20
+ marshaller.canonicalize(definition, input, context, validator, **opts)
21
+ end
22
+
23
+ def marshal_canonical(parameter, format, **opts)
24
+ marshaller = instance @canonical
25
+ if marshaller.nil?
26
+ value = parameter.send(:bare_value)
27
+ raise ParamsReadyError, "Value is not canonical" unless value.is_a? @canonical
28
+ value
29
+ else
30
+ marshaller.marshal(parameter, format, **opts)
31
+ end
32
+ end
33
+
34
+ def marshal(parameter, format, **opts)
35
+ default.marshal(parameter, format, **opts)
36
+ end
37
+
38
+ def infer_class(value)
39
+ if instances.key? value.class
40
+ value.class
41
+ elsif value.is_a?(Hash) || Extensions::Hash.acts_as_hash?(value)
42
+ Hash
43
+ else
44
+ value.class
45
+ end
46
+ end
47
+
48
+ def add_instance(value_class, instance)
49
+ raise ParamsReadyError, "Marshaller must be frozen" unless instance.frozen?
50
+
51
+ @instances[value_class] = instance
52
+ end
53
+
54
+ def instance(value_class)
55
+ @instances[value_class]
56
+ end
57
+
58
+ def instance?(value_class)
59
+ @instances.key?(value_class)
60
+ end
61
+
62
+ def default=(instance)
63
+ raise ParamsReadyError, "Default already defined" if default?
64
+ raise ParamsReadyError, "Marshaller must be frozen" unless instance.frozen?
65
+
66
+ @default = instance
67
+ end
68
+
69
+ def default!(value_class)
70
+ instance = instance(value_class)
71
+ raise ParamsReadyError, "No marshaller for class '#{value_class.name}'" if instance.nil?
72
+ self.default = instance
73
+ end
74
+
75
+ def default?
76
+ !@default.nil?
77
+ end
78
+
79
+ def reverse_merge(other)
80
+ clone = self.class.new(@canonical, @default, @instances.dup)
81
+ populate_clone(clone, other)
82
+ end
83
+
84
+ def populate_clone(clone, other)
85
+ if other.default? && !clone.default?
86
+ clone.default = other.default
87
+ end
88
+
89
+ other.instances.each do |value_class, i|
90
+ next if clone.instance?(value_class)
91
+
92
+ clone.add_instance value_class, i
93
+ end
94
+
95
+ clone
96
+ end
97
+
98
+ def freeze
99
+ @instance.freeze
100
+ super
101
+ end
102
+ end
103
+
104
+ class ClassCollection < InstanceCollection
105
+ attr_reader :factories
106
+
107
+ def initialize(canonical, default = nil, instances = {}, factories = {})
108
+ @factories = factories
109
+ super canonical, default, instances
110
+ end
111
+
112
+ def instance_collection
113
+ InstanceCollection.new(@canonical, nil, @instances.dup)
114
+ end
115
+
116
+ def add_factory(name, factory)
117
+ name = name.to_sym
118
+ raise ParamsReadyError, "Name '#{name}' already taken" if factory?(name)
119
+ raise ParamsReadyError, "Factory must be frozen" unless factory.frozen?
120
+
121
+ @factories[name] = factory
122
+ end
123
+
124
+ def add_instance(value_class, instance)
125
+ raise ParamsReadyError, "Marshaller for '#{value_class.name}' already exists" if instance?(value_class)
126
+
127
+ super
128
+ end
129
+
130
+ def build_instance(name, **opts)
131
+ factory(name).instance(**opts)
132
+ end
133
+
134
+ def factory(name)
135
+ @factories[name]
136
+ end
137
+
138
+ def factory?(name)
139
+ @factories.key? name
140
+ end
141
+
142
+ def freeze
143
+ @factories.freeze
144
+ super
145
+ end
146
+
147
+ def reverse_merge(other)
148
+ clone = self.class.new(@canonical, @default, @instances.dup, @factories.dup)
149
+ populate_clone(clone, other)
150
+ end
151
+
152
+ def populate_clone(clone, other)
153
+ merged = super
154
+
155
+ other.factories.each do |value_class, f|
156
+ next if merged.factory?(value_class)
157
+
158
+ clone.add_factory value_class, f
159
+ end
160
+
161
+ merged
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,63 @@
1
+ module ParamsReady
2
+ module Marshaller
3
+ module DefinitionModule
4
+ def self.[](collection)
5
+ mod = Module.new
6
+ mod.include self
7
+ mod.define_method :class_marshallers do
8
+ collection
9
+ end
10
+ mod
11
+ end
12
+
13
+ attr_reader :marshallers
14
+
15
+ def initialize(*args, marshaller: nil, **options)
16
+ @marshallers = class_marshallers.instance_collection
17
+ set_marshaller(**marshaller) unless marshaller.nil?
18
+
19
+ super *args, **options
20
+ end
21
+
22
+ def set_marshaller(to: nil, using: nil, **opts)
23
+ if using.is_a? Symbol
24
+ raise ParamsReadyError, "Expected ':to' argument to be nil, got #{to.class.name}" unless to.nil?
25
+ default_class, instance = class_marshallers.build_instance(using, **opts)
26
+ @marshallers.add_instance(default_class, instance)
27
+ @marshallers.default!(default_class)
28
+ elsif using.nil?
29
+ @marshallers.default!(to)
30
+ else
31
+ @marshallers.add_instance(to, using)
32
+ @marshallers.default!(to)
33
+ end
34
+ end
35
+
36
+ def marshal(parameter, intent, **opts)
37
+ if intent.marshal?(name_for_formatter)
38
+ @marshallers.marshal(parameter, intent, **opts)
39
+ else
40
+ @marshallers.marshal_canonical(parameter, intent, **opts)
41
+ end
42
+ end
43
+
44
+ def try_canonicalize(input, context, validator = nil, **opts)
45
+ @marshallers.canonicalize(self, input, context, validator, **opts)
46
+ end
47
+
48
+ def finish
49
+ unless @marshallers.default?
50
+ if class_marshallers.default?
51
+ @marshallers.default = class_marshallers.default
52
+ end
53
+ end
54
+ super
55
+ end
56
+
57
+ def freeze
58
+ @marshallers.freeze
59
+ super
60
+ end
61
+ end
62
+ end
63
+ end