optify-from_hash 0.3.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/lib/optify_from_hash/from_hashable.rb +69 -30
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23bb51f6e184777cad52c5ee7555c00a74e981b13c34fbc13133e80e4dd8b447
|
|
4
|
+
data.tar.gz: 80d4b0194cabc3202e60199ee7473f176e00795c9d2d07906cafca0cf0d9efe9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d51c60abe1041073c0090b6759824963af316248b858608ce7be0f8cf101f74d8207e2d25dbaab274868548c34dd2f4f067cf7807d732f34d0c91fae157afd59
|
|
7
|
+
data.tar.gz: db3e5143e8ca29bf947043d9cfaffd6d840bf8dc7295d546daefa7a65373b53a0606c48ea770338aaa7079e2652e94b5f24cc4baf877a05b2b2cd3cac41e6d1d
|
|
@@ -13,6 +13,41 @@ module Optify
|
|
|
13
13
|
extend T::Helpers
|
|
14
14
|
abstract!
|
|
15
15
|
|
|
16
|
+
@key_to_type = {} #: Hash[Symbol, T::Types::Base]
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
#: Hash[Symbol, T::Types::Base]
|
|
20
|
+
attr_reader :key_to_type
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: [T < Optify::FromHashable] (Class[T]) -> void
|
|
24
|
+
def self.inherited(subclass)
|
|
25
|
+
super
|
|
26
|
+
|
|
27
|
+
# Trace the execution after the subclass finishes loading to capture its methods.
|
|
28
|
+
TracePoint.trace(:end) do |tp|
|
|
29
|
+
if tp.self == subclass
|
|
30
|
+
subclass.instance_variable_set(:@key_to_type, _build_key_to_type(subclass))
|
|
31
|
+
tp.disable
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: [Type < Optify::FromHashable] (Class[Type]) -> Hash[Symbol, T::Types::Base]
|
|
37
|
+
private_class_method def self._build_key_to_type(subclass)
|
|
38
|
+
result = {}
|
|
39
|
+
|
|
40
|
+
subclass.public_instance_methods(false).each do |method_name|
|
|
41
|
+
method = subclass.instance_method(method_name)
|
|
42
|
+
sig = T::Utils.signature_for_method(method)
|
|
43
|
+
next if sig.nil?
|
|
44
|
+
|
|
45
|
+
result[method_name] = sig.return_type
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
result.freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
16
51
|
# Create a new immutable instance of the class from a hash.
|
|
17
52
|
#
|
|
18
53
|
# @param hash The hash to create the instance from.
|
|
@@ -22,34 +57,43 @@ module Optify
|
|
|
22
57
|
instance = new
|
|
23
58
|
|
|
24
59
|
hash.each do |key, value|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
rescue StandardError
|
|
28
|
-
raise ArgumentError,
|
|
29
|
-
"Error converting hash to `#{name}` because of key \"#{key}\". Perhaps \"#{key}\" is not a valid attribute for `#{name}`."
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
sig = T::Utils.signature_for_method(method)
|
|
33
|
-
raise "A Sorbet signature is required for `#{name}.#{key}`." if sig.nil?
|
|
34
|
-
|
|
35
|
-
sig_return_type = sig.return_type
|
|
36
|
-
value = _convert_value(value, sig_return_type)
|
|
60
|
+
value_type = _get_value_type(key)
|
|
61
|
+
value = _convert_value(value, value_type)
|
|
37
62
|
instance.instance_variable_set("@#{key}", value)
|
|
38
63
|
end
|
|
39
64
|
|
|
40
65
|
instance.freeze
|
|
41
66
|
end
|
|
42
67
|
|
|
68
|
+
#: (untyped) -> T::Types::Base
|
|
69
|
+
private_class_method def self._get_value_type(key)
|
|
70
|
+
key = key.to_sym
|
|
71
|
+
@key_to_type.fetch(key) do
|
|
72
|
+
parent = superclass #: untyped
|
|
73
|
+
while parent != Object
|
|
74
|
+
if parent.respond_to?(:key_to_type)
|
|
75
|
+
result = parent.key_to_type[key]
|
|
76
|
+
return result if result
|
|
77
|
+
end
|
|
78
|
+
parent = parent.superclass
|
|
79
|
+
end
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"Error converting hash to `#{name}` because no type was found for key \"#{key}\". " \
|
|
82
|
+
"Perhaps \"#{key}\" is not a valid attribute for `#{name}`. " \
|
|
83
|
+
"Types exist for #{@key_to_type.keys.sort!}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
43
87
|
#: (Array[untyped], untyped) -> (Array[untyped] | Set[untyped])
|
|
44
|
-
def self._convert_array(value, unwrapped_type)
|
|
88
|
+
private_class_method def self._convert_array(value, unwrapped_type)
|
|
45
89
|
inner_type = unwrapped_type.type
|
|
46
90
|
return value.map { |v| _convert_value(v, inner_type) }.freeze if unwrapped_type.is_a?(T::Types::TypedArray)
|
|
47
91
|
|
|
48
|
-
Set.new
|
|
92
|
+
value.each_with_object(Set.new) { |v, set| set.add(_convert_value(v, inner_type)) }.freeze
|
|
49
93
|
end
|
|
50
94
|
|
|
51
95
|
#: (untyped, T::Types::Base) -> untyped
|
|
52
|
-
def self._convert_value(value, type)
|
|
96
|
+
private_class_method def self._convert_value(value, type)
|
|
53
97
|
if type.is_a?(T::Types::Untyped)
|
|
54
98
|
# No preferred type is given, so return the value as is.
|
|
55
99
|
return value
|
|
@@ -84,7 +128,7 @@ module Optify
|
|
|
84
128
|
end
|
|
85
129
|
|
|
86
130
|
#: (Hash[untyped, untyped], T::Types::Base) -> untyped
|
|
87
|
-
def self._convert_hash(hash, type)
|
|
131
|
+
private_class_method def self._convert_hash(hash, type)
|
|
88
132
|
if type.respond_to?(:raw_type)
|
|
89
133
|
# There is an object for the hash.
|
|
90
134
|
# It could be a custom class, a String, or maybe something else.
|
|
@@ -94,15 +138,14 @@ module Optify
|
|
|
94
138
|
elsif type.is_a?(T::Types::TypedHash)
|
|
95
139
|
# The hash should be a hash, but the values might be objects to convert.
|
|
96
140
|
type_for_keys = type.keys
|
|
141
|
+
type_for_values = type.values
|
|
97
142
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
else
|
|
101
|
-
lambda(&:itself)
|
|
102
|
-
end
|
|
143
|
+
result = hash
|
|
144
|
+
.transform_values { |v| _convert_value(v, type_for_values) }
|
|
103
145
|
|
|
104
|
-
|
|
105
|
-
|
|
146
|
+
return result.transform_keys!(&:to_sym) if type_for_keys.is_a?(T::Types::Simple) && type_for_keys.raw_type == Symbol
|
|
147
|
+
|
|
148
|
+
return result
|
|
106
149
|
end
|
|
107
150
|
|
|
108
151
|
raise TypeError, "Could not convert hash #{hash} to `#{type}`."
|
|
@@ -110,7 +153,7 @@ module Optify
|
|
|
110
153
|
|
|
111
154
|
# Unwrap `T.nilable(...)` to get the inner type, or return the type as-is.
|
|
112
155
|
#: (T::Types::Base) -> T::Types::Base
|
|
113
|
-
def self._unwrap_nilable(type)
|
|
156
|
+
private_class_method def self._unwrap_nilable(type)
|
|
114
157
|
if type.respond_to?(:unwrap_nilable)
|
|
115
158
|
type #: as untyped
|
|
116
159
|
.unwrap_nilable
|
|
@@ -119,8 +162,6 @@ module Optify
|
|
|
119
162
|
end
|
|
120
163
|
end
|
|
121
164
|
|
|
122
|
-
private_class_method :_convert_array, :_convert_hash, :_convert_value, :_unwrap_nilable
|
|
123
|
-
|
|
124
165
|
# Compare this object with another object for equality.
|
|
125
166
|
# @param other The object to compare.
|
|
126
167
|
# @return [Boolean] true if the objects are equal; otherwise, false.
|
|
@@ -177,9 +218,9 @@ module Optify
|
|
|
177
218
|
end
|
|
178
219
|
|
|
179
220
|
#: (untyped) -> untyped
|
|
180
|
-
def self._convert_value_for_to_h(value)
|
|
221
|
+
private_class_method def self._convert_value_for_to_h(value)
|
|
181
222
|
case value
|
|
182
|
-
# Treat sets like arrays for JSON serialization.
|
|
223
|
+
# Treat sets like arrays for JSON serialization; otherwise, the elements are not shown.
|
|
183
224
|
when Array, Set
|
|
184
225
|
value.map { |v| _convert_value_for_to_h(v) }
|
|
185
226
|
when Hash
|
|
@@ -194,7 +235,5 @@ module Optify
|
|
|
194
235
|
end
|
|
195
236
|
end
|
|
196
237
|
end
|
|
197
|
-
|
|
198
|
-
private_class_method :_convert_value_for_to_h
|
|
199
238
|
end
|
|
200
239
|
end
|