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,38 @@
1
+ require_relative '../error'
2
+
3
+ module ParamsReady
4
+ module Extensions
5
+ module LateInit
6
+ def late_init(
7
+ name,
8
+ obligatory: true,
9
+ freeze: true,
10
+ getter: true,
11
+ boolean: false,
12
+ once: true,
13
+ definite: true,
14
+ &block
15
+ )
16
+ ivar = :"@#{name}"
17
+ define_method "set_#{name}" do |value|
18
+ raise ParamsReadyError, "Can't initialize '#{name}' to nil" if value.nil? && definite
19
+ value = instance_exec(value, &block) unless block.nil?
20
+ next if value == Extensions::Undefined
21
+
22
+ current = instance_variable_get ivar
23
+ raise ParamsReadyError, "Variable '#{name}' already set" unless current.nil? || !once
24
+ value.freeze if freeze
25
+ instance_variable_set "@#{name}", value
26
+ end
27
+
28
+ if boolean
29
+ define_method "#{name}?" do
30
+ instance_variable_get ivar
31
+ end
32
+ end
33
+ attr_reader name if getter
34
+ obligatory! name if obligatory
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ module ParamsReady
2
+ module Extensions
3
+ module Registry
4
+ def registry(registry_name, name_method: nil, as: nil, getter: false, &block)
5
+ class_variable_name = "@@#{registry_name}"
6
+
7
+ class_variable_set class_variable_name, {}
8
+ if as
9
+ if name_method.nil?
10
+ define_singleton_method "register_#{as}" do |name, entry|
11
+ registry = class_variable_get class_variable_name
12
+ raise ParamsReadyError, "Name '#{name}' already taken for '#{human_string(as)}'" if registry.key? name
13
+ instance_exec name, entry, &block unless block.nil?
14
+ registry[name] = entry
15
+ end
16
+ else
17
+ define_singleton_method "register_#{as}" do |entry|
18
+ name = entry.send name_method
19
+ registry = class_variable_get class_variable_name
20
+ raise ParamsReadyError, "Name '#{name}' already taken for '#{human_string(as)}'" if registry.key? name
21
+ instance_exec name, entry, &block unless block.nil?
22
+ registry[name] = entry
23
+ end
24
+ end
25
+ end
26
+ if getter
27
+ define_singleton_method as do |name|
28
+ registry = class_variable_get class_variable_name
29
+ raise ParamsReadyError, "Name '#{name}' not found in #{human_string(registry_name)}" unless registry.key? name
30
+ registry[name]
31
+ end
32
+ end
33
+ define_singleton_method "has_#{as}?" do |name|
34
+ registry = class_variable_get class_variable_name
35
+ registry.key? name
36
+ end
37
+ end
38
+
39
+ def human_string(string)
40
+ string.to_s.gsub("_", " ")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ module ParamsReady
2
+ module Extensions
3
+ class Undefined
4
+ def self.dup
5
+ self
6
+ end
7
+
8
+ def self.value_indefinite?(value)
9
+ value == self || value.nil?
10
+ end
11
+
12
+ freeze
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,130 @@
1
+ require 'set'
2
+ require_relative 'error'
3
+ require_relative 'helpers/rule'
4
+
5
+ module ParamsReady
6
+ class Format
7
+ module Wrapper
8
+ attr_reader :format
9
+
10
+ extend Forwardable
11
+ def_delegators :format,
12
+ :alternative?,
13
+ :standard?,
14
+ :hash_key,
15
+ :marshal,
16
+ :marshal?,
17
+ :remap?,
18
+ :local?,
19
+ :name
20
+ end
21
+
22
+ attr_reader :marshal, :naming_scheme, :name, :hash
23
+
24
+ OMIT_ALL = %i(undefined nil default no_output).freeze
25
+ def initialize(marshal:, naming_scheme:, remap:, omit:, local:, name: nil)
26
+ @marshal = Helpers::Rule(marshal)
27
+ @naming_scheme = naming_scheme
28
+ @remap = remap
29
+ @omit = omit.to_set.freeze
30
+ @local = local
31
+ @name = name.nil? ? name : name.to_sym
32
+ @hash = [@marshal, @naming_scheme, @remap, @omit, @local, @name].hash
33
+ freeze
34
+ end
35
+
36
+ def ==(other)
37
+ return false unless other.is_a? Format
38
+ return true if self.object_id == other.object_id
39
+ return false unless marshal == other.marshal
40
+ return false unless naming_scheme == other.naming_scheme
41
+ return false unless remap? == other.remap?
42
+ return false unless @omit == other.instance_variable_get(:@omit)
43
+ return false unless local? == other.local?
44
+ return false unless name == other.name
45
+
46
+ true
47
+ end
48
+
49
+ def alternative?
50
+ @naming_scheme == :alternative
51
+ end
52
+
53
+ def standard?
54
+ @naming_scheme == :standard
55
+ end
56
+
57
+ def remap?
58
+ @remap
59
+ end
60
+
61
+ def hash_key(parameter)
62
+ case @naming_scheme
63
+ when :standard then parameter.name
64
+ when :alternative then parameter.altn
65
+ else
66
+ raise ParamsReadyError, "Unexpected option: #{@naming_scheme}"
67
+ end
68
+ end
69
+
70
+ def omit?(parameter)
71
+ return true if parameter.no_output?(self)
72
+ return true if parameter.is_undefined? && @omit.member?(:undefined)
73
+ return true if parameter.is_nil? && @omit.member?(:nil)
74
+ return true if parameter.is_default? && @omit.member?(:default)
75
+ false
76
+ end
77
+
78
+ def local?
79
+ @local
80
+ end
81
+
82
+ def preserve?(parameter)
83
+ !omit?(parameter)
84
+ end
85
+
86
+ def marshal?(type)
87
+ @marshal.include?(type)
88
+ end
89
+
90
+ def update(**opts)
91
+ opts = instance_variables.reject { |ivar| ivar == :@hash }.map do |ivar|
92
+ value = instance_variable_get(ivar)
93
+ name = ivar.to_s.gsub(/^@/, '').to_sym
94
+ [name, value]
95
+ end.to_h.merge(opts)
96
+
97
+ Format.new(**opts)
98
+ end
99
+
100
+ @names = {
101
+ backend: Format.new(marshal: :none, omit: [], naming_scheme: :standard, remap: false, local: true, name: :backend),
102
+ frontend: Format.new(marshal: :all, omit: OMIT_ALL, naming_scheme: :alternative, remap: false, local: false, name: :frontend),
103
+ attributes: Format.new(marshal: :none, omit: %i(undefined), naming_scheme: :standard, remap: false, local: true, name: :attributes),
104
+ json: Format.new(marshal: { except: [:array, :tuple, :boolean, :number] }, omit: [], naming_scheme: :alternative, remap: true, local: false, name: :json)
105
+ }.freeze
106
+
107
+ def self.define(name, format)
108
+ @names = @names.dup
109
+ @names[name] = format
110
+ @names.freeze
111
+ end
112
+
113
+ def self.instance(name)
114
+ raise ParamsReadyError, "Unknown format '#{name}'" unless @names.key? name
115
+ @names[name]
116
+ end
117
+
118
+ def self.resolve(format_or_name)
119
+ if format_or_name.is_a? Format
120
+ format_or_name
121
+ elsif format_or_name.is_a? Symbol
122
+ instance(format_or_name)
123
+ elsif format_or_name.respond_to? :format
124
+ format_or_name.format
125
+ else
126
+ raise ParamsReadyError, "Not an acceptable format: #{format_or_name}"
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../error'
2
+
3
+ module ParamsReady
4
+ module Helpers
5
+ class ArelBuilder
6
+ def self.instance(object, arel_table: nil)
7
+ case object
8
+ when Arel::Nodes::Node, Arel::Nodes::SqlLiteral, Arel::Attribute
9
+ raise ParamsReadyError, "Arel table unexpected" unless arel_table.nil? || arel_table == :none
10
+ ArelObject.new(object)
11
+ when Proc
12
+ raise ParamsReadyError, "Arel table unexpected" unless arel_table.nil? || arel_table == :none
13
+ Callable.new(object)
14
+ when String, Symbol
15
+ Literal.new(object, arel_table)
16
+ else
17
+ raise ParamsReadyError, "Unexpected type for arel builder: #{object.class.name}"
18
+ end
19
+ end
20
+
21
+ def self.safe_name(name)
22
+ name[0...64]
23
+ end
24
+
25
+ class Callable
26
+ def initialize(proc)
27
+ @proc = proc
28
+ end
29
+
30
+ def to_arel(*args)
31
+ result = @proc.call(*args)
32
+ case result
33
+ when String, Symbol
34
+ Helpers::ArelBuilder.instance(result).to_arel(*args)
35
+ else
36
+ result
37
+ end
38
+ end
39
+ end
40
+
41
+ class Literal
42
+ def initialize(literal, arel_table)
43
+ @literal = literal.to_s
44
+ @arel_table = arel_table
45
+ end
46
+
47
+ def to_arel(default_table, _, _)
48
+ arel_table = @arel_table || default_table
49
+ if arel_table == :none
50
+ Arel::Nodes::SqlLiteral.new(@literal)
51
+ else
52
+ arel_table[@literal]
53
+ end
54
+ end
55
+ end
56
+
57
+ class ArelObject
58
+ def initialize(node)
59
+ @node = node
60
+ end
61
+
62
+ def to_arel(_, _, _)
63
+ @node
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'rule'
2
+ require_relative '../error'
3
+
4
+ module ParamsReady
5
+ module Helpers
6
+ class Conditional
7
+ def initialize(rule: nil)
8
+ @rule = Helpers::Rule(rule)
9
+ freeze
10
+ end
11
+
12
+ def perform?(general_rule, name)
13
+ if @rule.nil?
14
+ general_rule
15
+ else
16
+ @rule.include?(name)
17
+ end
18
+ end
19
+ end
20
+
21
+ class ConditionalBlock < Conditional
22
+ attr_reader :block
23
+
24
+ def initialize(rule: nil, &block)
25
+ raise ParamsReadyError, "Block must not be empty" if block.nil?
26
+ @block = block
27
+ super(rule: rule)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../extensions/undefined'
2
+ require_relative 'key_map'
3
+
4
+ module ParamsReady
5
+ module Helpers
6
+ module FindInHash
7
+ def self.find_in_hash(hash, name_or_path)
8
+ return false, Extensions::Undefined if hash.nil?
9
+
10
+ found = if name_or_path.is_a? Array
11
+ *path, name = name_or_path
12
+ Helpers::KeyMap::Mapping::Path.dig(name, hash, path)
13
+ else
14
+ Extensions::Hash.indifferent_access(hash, name_or_path, Extensions::Undefined)
15
+ end
16
+
17
+ return false, Extensions::Undefined if found == Extensions::Undefined
18
+ return true, found
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,176 @@
1
+ require_relative '../error'
2
+ require_relative '../extensions/undefined'
3
+ require_relative '../extensions/hash'
4
+
5
+ module ParamsReady
6
+ module Helpers
7
+ class KeyMap
8
+ class Mapping
9
+ class Path
10
+ attr_reader :path, :names
11
+
12
+ def initialize(path, names = [])
13
+ @path = path.map(&:to_sym).freeze
14
+ @names = names
15
+ end
16
+
17
+ def add_names(names)
18
+ names.each{ |name| add_name(name) }
19
+ end
20
+
21
+ def add_name(name)
22
+ @names << name
23
+ end
24
+
25
+ def dig(name, hash)
26
+ self.class.dig(name, hash, @path)
27
+ end
28
+
29
+ def self.dig(name, hash, path)
30
+ result = path.reduce(hash) do |current, name|
31
+ next unless Extensions::Hash.acts_as_hash?(current)
32
+
33
+ Extensions::Hash.indifferent_access current, name, nil
34
+ end
35
+
36
+ return Extensions::Undefined unless Extensions::Hash.acts_as_hash?(result)
37
+
38
+ Extensions::Hash.indifferent_access result, name, Extensions::Undefined
39
+ end
40
+
41
+ def store(name, value, hash)
42
+ self.class.store(name, value, hash, @path)
43
+ end
44
+
45
+ def self.store(name, value, hash, path)
46
+ return if value == Extensions::Undefined
47
+
48
+ result = path.reduce(hash) do |current, name|
49
+ current[name] ||= {}
50
+ current[name]
51
+ end
52
+
53
+ result[name] = value
54
+ result
55
+ end
56
+
57
+ def =~(other)
58
+ raise ParamsReadyError, "Can't match path with #{other.class.name}" unless other.is_a? Path
59
+ path == other.path
60
+ end
61
+
62
+ def ==(other)
63
+ raise ParamsReadyError, "Can't compare path with #{other.class.name}" unless other.is_a? Path
64
+ path == other.path && names == other.names
65
+ end
66
+ end
67
+
68
+ def initialize(alt_path, alt_names, std_path, std_names)
69
+ if alt_names.length != std_names.length
70
+ msg = "Expected equal number of alternative and standard names, got #{alt_names.length}/#{std_names.length}"
71
+ raise ParamsReadyError, msg
72
+ end
73
+
74
+ @alt = Path.new(alt_path, alt_names)
75
+ @std = Path.new(std_path, std_names)
76
+ end
77
+
78
+ def add_names(altn, stdn)
79
+ @alt.add_name altn
80
+ @std.add_name stdn
81
+ end
82
+
83
+ def remap(from, to, input, target)
84
+ path(from).names.each_with_index do |input_name, idx|
85
+ value = dig(from, input_name, input)
86
+ target_name = path(to).names[idx]
87
+ store(to, target_name, value, target)
88
+ end
89
+ end
90
+
91
+ def merge!(other)
92
+ raise ParamsReadyError, "Can't merge non_matching mapping" unless self =~ other
93
+
94
+ @alt.add_names(other.alt.names)
95
+ @std.add_names(other.std.names)
96
+ end
97
+
98
+ def dig(schema, name, hash)
99
+ path(schema).dig(name, hash)
100
+ end
101
+
102
+ def store(schema, name, value, hash)
103
+ path(schema).store(name, value, hash)
104
+ end
105
+
106
+ def path(schema)
107
+ case schema
108
+ when :alt then @alt
109
+ when :std then @std
110
+ else
111
+ raise ParamsReadyError, "Unexpected naming schema: #{schema}"
112
+ end
113
+ end
114
+
115
+ def =~(other)
116
+ raise ParamsReadyError, "Can't match path with #{other.class.name}" unless other.is_a? Mapping
117
+ return false unless path(:alt) =~ other.path(:alt)
118
+ return false unless path(:std) =~ other.path(:std)
119
+
120
+ true
121
+ end
122
+
123
+ protected
124
+ attr_reader :alt, :std
125
+ end
126
+
127
+ def initialize
128
+ @mappings = []
129
+ end
130
+
131
+ def map(from, to:)
132
+ alt_path, alt_names = self.class.split_map(from)
133
+ std_path, std_names = self.class.split_map(to)
134
+ mapping = Mapping.new(alt_path, alt_names, std_path, std_names)
135
+ merge_or_add_mapping(mapping)
136
+ self
137
+ end
138
+
139
+ def merge_or_add_mapping(mapping)
140
+ if (existing = @mappings.find { |candidate| candidate =~ mapping })
141
+ existing.merge!(mapping)
142
+ else
143
+ @mappings << mapping
144
+ end
145
+ mapping
146
+ end
147
+
148
+ def to_standard(hash)
149
+ remap(:alt, :std, hash)
150
+ end
151
+
152
+ def to_alternative(hash)
153
+ remap(:std, :alt, hash)
154
+ end
155
+
156
+ def remap(from, to, input)
157
+ @mappings.each_with_object({}) do |mapping, result|
158
+ mapping.remap(from, to, input, result)
159
+ end
160
+ end
161
+
162
+ def freeze
163
+ @mappings.each(&:freeze)
164
+ super
165
+ end
166
+
167
+ def self.split_map(array)
168
+ raise ParamsReadyError, "Array expected, got: #{array.class.name}" unless array.is_a? Array
169
+ names = array.last || []
170
+ raise ParamsReadyError, "Array expected, got: #{names.class.name}" unless names.is_a? Array
171
+ paths = array[0...-1]
172
+ [paths, names]
173
+ end
174
+ end
175
+ end
176
+ end