dry-struct 1.0.0 → 1.6.0
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/CHANGELOG.md +246 -82
- data/LICENSE +17 -17
- data/README.md +18 -13
- data/dry-struct.gemspec +35 -35
- data/lib/dry/struct/class_interface.rb +139 -77
- data/lib/dry/struct/compiler.rb +19 -0
- data/lib/dry/struct/constructor.rb +4 -24
- data/lib/dry/struct/errors.rb +13 -3
- data/lib/dry/struct/extensions/pretty_print.rb +8 -6
- data/lib/dry/struct/extensions.rb +3 -1
- data/lib/dry/struct/hashify.rb +6 -6
- data/lib/dry/struct/printer.rb +6 -1
- data/lib/dry/struct/struct_builder.rb +46 -24
- data/lib/dry/struct/sum.rb +5 -5
- data/lib/dry/struct/value.rb +12 -4
- data/lib/dry/struct/version.rb +3 -1
- data/lib/dry/struct.rb +70 -35
- data/lib/dry-struct.rb +3 -1
- metadata +48 -54
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.travis.yml +0 -29
- data/.yardopts +0 -4
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -26
- data/Rakefile +0 -10
- data/benchmarks/basic.rb +0 -57
- data/benchmarks/constrained.rb +0 -37
- data/benchmarks/profile_instantiation.rb +0 -19
- data/benchmarks/setup.rb +0 -11
- data/bin/console +0 -12
- data/bin/setup +0 -7
- data/log/.gitkeep +0 -0
@@ -1,32 +1,22 @@
|
|
1
|
-
|
2
|
-
require 'dry/core/inflector'
|
3
|
-
require 'dry/core/descendants_tracker'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
require
|
6
|
-
require 'dry/struct/constructor'
|
7
|
-
require 'dry/struct/sum'
|
3
|
+
require "weakref"
|
8
4
|
|
9
5
|
module Dry
|
10
6
|
class Struct
|
11
7
|
# Class-level interface of {Struct} and {Value}
|
12
|
-
module ClassInterface
|
8
|
+
module ClassInterface # rubocop:disable Metrics/ModuleLength
|
13
9
|
include Core::ClassAttributes
|
14
10
|
|
15
|
-
include
|
16
|
-
include
|
11
|
+
include Types::Type
|
12
|
+
include Types::Builder
|
17
13
|
|
18
14
|
# @param [Class] klass
|
19
15
|
def inherited(klass)
|
20
16
|
super
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
klass.class_eval do
|
25
|
-
@meta = base.meta
|
26
|
-
|
27
|
-
unless equal?(Value)
|
28
|
-
extend Dry::Core::DescendantsTracker
|
29
|
-
end
|
18
|
+
unless klass.name.eql?("Dry::Struct::Value")
|
19
|
+
klass.extend(Core::DescendantsTracker)
|
30
20
|
end
|
31
21
|
end
|
32
22
|
|
@@ -52,8 +42,12 @@ module Dry
|
|
52
42
|
# end
|
53
43
|
# end
|
54
44
|
#
|
55
|
-
# Language.schema
|
56
|
-
# # => #<Dry::Types[
|
45
|
+
# Language.schema # new lines for readability
|
46
|
+
# # => #<Dry::Types[
|
47
|
+
# Constructor<Schema<keys={
|
48
|
+
# name: Constrained<Nominal<String> rule=[type?(String)]>
|
49
|
+
# details: Language::Details
|
50
|
+
# }> fn=Kernel.Hash>]>
|
57
51
|
#
|
58
52
|
# ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
|
59
53
|
# ruby.name #=> 'Ruby'
|
@@ -70,11 +64,13 @@ module Dry
|
|
70
64
|
# end
|
71
65
|
# end
|
72
66
|
#
|
73
|
-
# Language.schema
|
67
|
+
# Language.schema # new lines for readability
|
74
68
|
# => #<Dry::Types[Constructor<Schema<keys={
|
75
|
-
# name: Nominal<String>
|
76
|
-
# versions:
|
77
|
-
#
|
69
|
+
# name: Constrained<Nominal<String> rule=[type?(String)]>
|
70
|
+
# versions: Constrained<
|
71
|
+
# Array<Constrained<Nominal<String> rule=[type?(String)]>
|
72
|
+
# > rule=[type?(Array)]>
|
73
|
+
# celebrities: Constrained<Array<Language::Celebrity> rule=[type?(Array)]>
|
78
74
|
# }> fn=Kernel.Hash>]>
|
79
75
|
#
|
80
76
|
# ruby = Language.new(
|
@@ -96,19 +92,54 @@ module Dry
|
|
96
92
|
# ruby.celebrities[0].pseudonym #=> 'Matz'
|
97
93
|
# ruby.celebrities[1].name #=> 'Aaron Patterson'
|
98
94
|
# ruby.celebrities[1].pseudonym #=> 'tenderlove'
|
99
|
-
def attribute(name, type =
|
95
|
+
def attribute(name, type = Undefined, &block)
|
100
96
|
attributes(name => build_type(name, type, &block))
|
101
97
|
end
|
102
98
|
|
99
|
+
# Add atributes from another struct
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# class Address < Dry::Struct
|
103
|
+
# attribute :city, Types::String
|
104
|
+
# attribute :country, Types::String
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# class User < Dry::Struct
|
108
|
+
# attribute :name, Types::String
|
109
|
+
# attributes_from Address
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# User.new(name: 'Quispe', city: 'La Paz', country: 'Bolivia')
|
113
|
+
#
|
114
|
+
# @example with nested structs
|
115
|
+
# class User < Dry::Struct
|
116
|
+
# attribute :name, Types::String
|
117
|
+
# attribute :address do
|
118
|
+
# attributes_from Address
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# @param struct [Dry::Struct]
|
123
|
+
def attributes_from(struct)
|
124
|
+
extracted_schema = struct.schema.keys.map { |key|
|
125
|
+
if key.required?
|
126
|
+
[key.name, key.type]
|
127
|
+
else
|
128
|
+
[:"#{key.name}?", key.type]
|
129
|
+
end
|
130
|
+
}.to_h
|
131
|
+
attributes(extracted_schema)
|
132
|
+
end
|
133
|
+
|
103
134
|
# Adds an omittable (key is not required on initialization) attribute for this {Struct}
|
104
135
|
#
|
105
136
|
# @example
|
106
137
|
# class User < Dry::Struct
|
107
|
-
# attribute :name, Types::
|
108
|
-
# attribute? :email, Types::
|
138
|
+
# attribute :name, Types::String
|
139
|
+
# attribute? :email, Types::String
|
109
140
|
# end
|
110
141
|
#
|
111
|
-
# User.new(name: 'John') # => #<User name="John">
|
142
|
+
# User.new(name: 'John') # => #<User name="John" email=nil>
|
112
143
|
#
|
113
144
|
# @param [Symbol] name name of the defined attribute
|
114
145
|
# @param [Dry::Types::Type, nil] type or superclass of nested type
|
@@ -116,17 +147,17 @@ module Dry
|
|
116
147
|
#
|
117
148
|
def attribute?(*args, &block)
|
118
149
|
if args.size == 1 && block.nil?
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
tag: :
|
150
|
+
Core::Deprecations.warn(
|
151
|
+
"Dry::Struct.attribute? is deprecated for checking attribute presence, "\
|
152
|
+
"use has_attribute? instead",
|
153
|
+
tag: :"dry-struct"
|
123
154
|
)
|
124
155
|
|
125
156
|
has_attribute?(args[0])
|
126
157
|
else
|
127
|
-
name,
|
158
|
+
name, * = args
|
128
159
|
|
129
|
-
attribute(:"#{
|
160
|
+
attribute(:"#{name}?", build_type(*args, &block))
|
130
161
|
end
|
131
162
|
end
|
132
163
|
|
@@ -145,29 +176,22 @@ module Dry
|
|
145
176
|
#
|
146
177
|
# Book.schema
|
147
178
|
# # => #<Dry::Types[Constructor<Schema<keys={
|
148
|
-
# # title: Nominal<String>
|
149
|
-
# # author: Nominal<String>
|
179
|
+
# # title: Constrained<Nominal<String> rule=[type?(String)]>
|
180
|
+
# # author: Constrained<Nominal<String> rule=[type?(String)]>
|
150
181
|
# # }> fn=Kernel.Hash>]>
|
151
182
|
def attributes(new_schema)
|
152
|
-
keys = new_schema.keys.map { |k| k.to_s.chomp(
|
183
|
+
keys = new_schema.keys.map { |k| k.to_s.chomp("?").to_sym }
|
153
184
|
check_schema_duplication(keys)
|
154
185
|
|
155
186
|
schema schema.schema(new_schema)
|
156
187
|
|
157
|
-
keys
|
158
|
-
next if instance_methods.include?(key)
|
159
|
-
class_eval(<<-RUBY)
|
160
|
-
def #{key}
|
161
|
-
@attributes[#{key.inspect}]
|
162
|
-
end
|
163
|
-
RUBY
|
164
|
-
end
|
188
|
+
define_accessors(keys)
|
165
189
|
|
166
190
|
@attribute_names = nil
|
167
191
|
|
168
192
|
direct_descendants = descendants.select { |d| d.superclass == self }
|
169
193
|
direct_descendants.each do |d|
|
170
|
-
inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp(
|
194
|
+
inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp("?").to_sym) }
|
171
195
|
d.attributes(inherited_attrs)
|
172
196
|
end
|
173
197
|
|
@@ -182,7 +206,7 @@ module Dry
|
|
182
206
|
# class Book < Dry::Struct
|
183
207
|
# transform_types { |t| t.meta(struct: :Book) }
|
184
208
|
#
|
185
|
-
# attribute :title, Types::
|
209
|
+
# attribute :title, Types::String
|
186
210
|
# end
|
187
211
|
#
|
188
212
|
# Book.schema.key(:title).meta # => { struct: :Book }
|
@@ -199,7 +223,7 @@ module Dry
|
|
199
223
|
# class Book < Dry::Struct
|
200
224
|
# transform_keys(&:to_sym)
|
201
225
|
#
|
202
|
-
# attribute :title, Types::
|
226
|
+
# attribute :title, Types::String
|
203
227
|
# end
|
204
228
|
#
|
205
229
|
# Book.new('title' => "The Old Man and the Sea")
|
@@ -208,7 +232,7 @@ module Dry
|
|
208
232
|
schema schema.with_key_transform(proc || block)
|
209
233
|
end
|
210
234
|
|
211
|
-
# @param [Hash{Symbol => Dry::Types::Type, Dry::Struct}]
|
235
|
+
# @param [Hash{Symbol => Dry::Types::Type, Dry::Struct}] new_keys
|
212
236
|
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
213
237
|
# same name as previously defined one
|
214
238
|
def check_schema_duplication(new_keys)
|
@@ -222,16 +246,25 @@ module Dry
|
|
222
246
|
|
223
247
|
# @param [Hash{Symbol => Object},Dry::Struct] attributes
|
224
248
|
# @raise [Struct::Error] if the given attributes don't conform {#schema}
|
225
|
-
def new(attributes = default_attributes, safe = false)
|
226
|
-
if
|
227
|
-
attributes
|
249
|
+
def new(attributes = default_attributes, safe = false, &block) # rubocop:disable Style/OptionalBooleanParameter
|
250
|
+
if attributes.is_a?(Struct)
|
251
|
+
if equal?(attributes.class)
|
252
|
+
attributes
|
253
|
+
else
|
254
|
+
# This implicit coercion is arguable but makes sense overall
|
255
|
+
# in cases there you pass child struct to the base struct constructor
|
256
|
+
# User.new(super_user)
|
257
|
+
#
|
258
|
+
# We may deprecate this behavior in future forcing people to be explicit
|
259
|
+
new(attributes.to_h, safe, &block)
|
260
|
+
end
|
228
261
|
elsif safe
|
229
262
|
load(schema.call_safe(attributes) { |output = attributes| return yield output })
|
230
263
|
else
|
231
264
|
load(schema.call_unsafe(attributes))
|
232
265
|
end
|
233
|
-
rescue Types::CoercionError =>
|
234
|
-
raise
|
266
|
+
rescue Types::CoercionError => e
|
267
|
+
raise Error, "[#{self}.new] #{e}", e.backtrace
|
235
268
|
end
|
236
269
|
|
237
270
|
# @api private
|
@@ -255,27 +288,26 @@ module Dry
|
|
255
288
|
# @api private
|
256
289
|
def load(attributes)
|
257
290
|
struct = allocate
|
258
|
-
struct.
|
291
|
+
struct.__send__(:initialize, attributes)
|
259
292
|
struct
|
260
293
|
end
|
261
294
|
|
262
295
|
# @param [#call,nil] constructor
|
263
|
-
# @param [Hash] _options
|
264
296
|
# @param [#call,nil] block
|
265
297
|
# @return [Dry::Struct::Constructor]
|
266
|
-
def constructor(constructor = nil,
|
267
|
-
|
298
|
+
def constructor(constructor = nil, **, &block)
|
299
|
+
Constructor[self, fn: constructor || block]
|
268
300
|
end
|
269
301
|
|
270
302
|
# @param [Hash{Symbol => Object},Dry::Struct] input
|
271
303
|
# @yieldparam [Dry::Types::Result::Failure] failure
|
272
|
-
# @yieldreturn [Dry::Types::
|
304
|
+
# @yieldreturn [Dry::Types::Result]
|
273
305
|
# @return [Dry::Types::Result]
|
274
306
|
def try(input)
|
275
|
-
|
276
|
-
rescue
|
277
|
-
|
278
|
-
block_given? ? yield(
|
307
|
+
success(self[input])
|
308
|
+
rescue Error => e
|
309
|
+
failure_result = failure(input, e)
|
310
|
+
block_given? ? yield(failure_result) : failure_result
|
279
311
|
end
|
280
312
|
|
281
313
|
# @param [Hash{Symbol => Object},Dry::Struct] input
|
@@ -298,7 +330,7 @@ module Dry
|
|
298
330
|
# @param [({Symbol => Object})] args
|
299
331
|
# @return [Dry::Types::Result::Failure]
|
300
332
|
def failure(*args)
|
301
|
-
result(
|
333
|
+
result(Types::Result::Failure, *args)
|
302
334
|
end
|
303
335
|
|
304
336
|
# @param [Class] klass
|
@@ -312,7 +344,7 @@ module Dry
|
|
312
344
|
false
|
313
345
|
end
|
314
346
|
|
315
|
-
# @param [Object, Dry::Struct]
|
347
|
+
# @param [Object, Dry::Struct] other
|
316
348
|
# @return [Boolean]
|
317
349
|
def ===(other)
|
318
350
|
other.is_a?(self)
|
@@ -336,7 +368,7 @@ module Dry
|
|
336
368
|
|
337
369
|
# @return [Proc]
|
338
370
|
def to_proc
|
339
|
-
proc { |input| call(input) }
|
371
|
+
@to_proc ||= proc { |input| call(input) }
|
340
372
|
end
|
341
373
|
|
342
374
|
# Checks if this {Struct} has the given attribute
|
@@ -357,10 +389,12 @@ module Dry
|
|
357
389
|
# @return [{Symbol => Object}]
|
358
390
|
def meta(meta = Undefined)
|
359
391
|
if meta.equal?(Undefined)
|
360
|
-
|
392
|
+
schema.meta
|
393
|
+
elsif meta.empty?
|
394
|
+
self
|
361
395
|
else
|
362
|
-
Class.new(self) do
|
363
|
-
|
396
|
+
::Class.new(self) do
|
397
|
+
schema schema.meta(meta) unless meta.empty?
|
364
398
|
end
|
365
399
|
end
|
366
400
|
end
|
@@ -369,13 +403,28 @@ module Dry
|
|
369
403
|
# @param [Dry::Types::Type] type
|
370
404
|
# @return [Dry::Types::Sum]
|
371
405
|
def |(type)
|
372
|
-
if type.is_a?(Class) && type <= Struct
|
373
|
-
|
406
|
+
if type.is_a?(::Class) && type <= Struct
|
407
|
+
Sum.new(self, type)
|
374
408
|
else
|
375
409
|
super
|
376
410
|
end
|
377
411
|
end
|
378
412
|
|
413
|
+
# Make the struct abstract. This class will be used as a default
|
414
|
+
# parent class for nested structs
|
415
|
+
def abstract
|
416
|
+
abstract_class self
|
417
|
+
end
|
418
|
+
|
419
|
+
# Dump to the AST
|
420
|
+
#
|
421
|
+
# @return [Array]
|
422
|
+
#
|
423
|
+
# @api public
|
424
|
+
def to_ast(meta: true)
|
425
|
+
[:struct, [::WeakRef.new(self), schema.to_ast(meta: meta)]]
|
426
|
+
end
|
427
|
+
|
379
428
|
# Stores an object for building nested struct classes
|
380
429
|
# @return [StructBuilder]
|
381
430
|
def struct_builder
|
@@ -399,21 +448,21 @@ module Dry
|
|
399
448
|
# @param [Dry::Types::Type] type
|
400
449
|
# @return [Boolean]
|
401
450
|
def struct?(type)
|
402
|
-
type.is_a?(Class) && type <= Struct
|
451
|
+
type.is_a?(::Class) && type <= Struct
|
403
452
|
end
|
404
453
|
private :struct?
|
405
454
|
|
406
455
|
# Constructs a type
|
407
456
|
#
|
408
457
|
# @return [Dry::Types::Type, Dry::Struct]
|
409
|
-
def build_type(name, type, &block)
|
458
|
+
def build_type(name, type = Undefined, &block)
|
410
459
|
type_object =
|
411
|
-
if type.is_a?(String)
|
412
|
-
|
413
|
-
elsif block.nil? &&
|
460
|
+
if type.is_a?(::String)
|
461
|
+
Types[type]
|
462
|
+
elsif block.nil? && Undefined.equal?(type)
|
414
463
|
raise(
|
415
|
-
ArgumentError,
|
416
|
-
|
464
|
+
::ArgumentError,
|
465
|
+
"you must supply a type or a block to `Dry::Struct.attribute`"
|
417
466
|
)
|
418
467
|
else
|
419
468
|
type
|
@@ -426,6 +475,19 @@ module Dry
|
|
426
475
|
end
|
427
476
|
end
|
428
477
|
private :build_type
|
478
|
+
|
479
|
+
def define_accessors(keys)
|
480
|
+
keys.each do |key|
|
481
|
+
next if instance_methods.include?(key)
|
482
|
+
|
483
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
484
|
+
def #{key} # def email
|
485
|
+
@attributes[#{key.inspect}] # @attributes[:email]
|
486
|
+
end # end
|
487
|
+
RUBY
|
488
|
+
end
|
489
|
+
end
|
490
|
+
private :define_accessors
|
429
491
|
end
|
430
492
|
end
|
431
493
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Struct
|
5
|
+
class Compiler < Types::Compiler
|
6
|
+
def visit_struct(node)
|
7
|
+
struct, _ = node
|
8
|
+
|
9
|
+
struct.__getobj__
|
10
|
+
rescue ::WeakRef::RefError
|
11
|
+
if struct.weakref_alive?
|
12
|
+
raise
|
13
|
+
else
|
14
|
+
raise RecycledStructError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,29 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
|
-
class Constructor
|
4
|
-
|
5
|
-
include Dry::Types::Type
|
6
|
-
|
7
|
-
# @return [#call]
|
8
|
-
attr_reader :fn
|
9
|
-
|
10
|
-
# @return [#call]
|
11
|
-
attr_reader :type
|
12
|
-
|
13
|
-
# @param [Struct] type
|
14
|
-
# @param [Hash] options
|
15
|
-
# @param [#call, nil] block
|
16
|
-
def initialize(type, options = {}, &block)
|
17
|
-
@type = type
|
18
|
-
@fn = options.fetch(:fn, block)
|
19
|
-
end
|
20
|
-
|
21
|
-
# @param [Object] input
|
22
|
-
# @return [Object]
|
23
|
-
def call(input)
|
24
|
-
type[fn[input]]
|
25
|
-
end
|
26
|
-
alias_method :[], :call
|
5
|
+
class Constructor < Types::Constructor
|
6
|
+
alias_method :primitive, :type
|
27
7
|
end
|
28
8
|
end
|
29
9
|
end
|
data/lib/dry/struct/errors.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
5
|
# Raised when given input doesn't conform schema and constructor type
|
4
6
|
Error = Class.new(TypeError)
|
5
7
|
|
6
8
|
# Raised when defining duplicate attributes
|
7
|
-
class RepeatedAttributeError < ArgumentError
|
9
|
+
class RepeatedAttributeError < ::ArgumentError
|
8
10
|
# @param [Symbol] key
|
9
11
|
# attribute name that is the same as previously defined one
|
10
12
|
def initialize(key)
|
@@ -13,9 +15,17 @@ module Dry
|
|
13
15
|
end
|
14
16
|
|
15
17
|
# Raised when a struct doesn't have an attribute
|
16
|
-
class MissingAttributeError < KeyError
|
18
|
+
class MissingAttributeError < ::KeyError
|
17
19
|
def initialize(key)
|
18
|
-
super("Missing attribute: #{
|
20
|
+
super("Missing attribute: #{key.inspect}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# When struct class stored in ast was garbage collected because no alive objects exists
|
25
|
+
# This shouldn't happen in a working application
|
26
|
+
class RecycledStructError < ::RuntimeError
|
27
|
+
def initialize
|
28
|
+
super("Reference to struct class was garbage collected")
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pp"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
class Struct
|
5
7
|
def pretty_print(pp)
|
6
8
|
klass = self.class
|
7
|
-
pp.group(1, "#<#{
|
8
|
-
pp.seplist(@attributes.keys, proc { pp.text
|
9
|
+
pp.group(1, "#<#{klass.name || klass.inspect}", ">") do
|
10
|
+
pp.seplist(@attributes.keys, proc { pp.text "," }) do |column_name|
|
9
11
|
column_value = @attributes[column_name]
|
10
|
-
pp.breakable
|
12
|
+
pp.breakable " "
|
11
13
|
pp.group(1) do
|
12
|
-
pp.text column_name
|
13
|
-
pp.text
|
14
|
+
pp.text column_name.to_s
|
15
|
+
pp.text "="
|
14
16
|
pp.pp column_value
|
15
17
|
end
|
16
18
|
end
|
data/lib/dry/struct/hashify.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
5
|
# Helper for {Struct#to_hash} implementation
|
@@ -6,12 +8,10 @@ module Dry
|
|
6
8
|
# @param [#to_hash, #map, Object] value
|
7
9
|
# @return [Hash, Array]
|
8
10
|
def self.[](value)
|
9
|
-
if value.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
value.to_hash.each_with_object({}) { |(k, v), h| h[k] = self[v] }
|
14
|
-
end
|
11
|
+
if value.is_a?(Struct)
|
12
|
+
value.to_h.transform_values { |current| self[current] }
|
13
|
+
elsif value.respond_to?(:to_hash)
|
14
|
+
value.to_hash.transform_values { |current| self[current] }
|
15
15
|
elsif value.respond_to?(:to_ary)
|
16
16
|
value.to_ary.map { |item| self[item] }
|
17
17
|
else
|
data/lib/dry/struct/printer.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/types/printer"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
5
7
|
# @api private
|
6
8
|
class Printer
|
7
9
|
MAPPING[Struct::Sum] = :visit_struct_sum
|
10
|
+
MAPPING[Struct::Constructor] = :visit_struct_constructor
|
8
11
|
|
9
12
|
def visit_struct_sum(sum)
|
10
13
|
visit_sum_constructors(sum) do |constructors|
|
@@ -13,6 +16,8 @@ module Dry
|
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
19
|
+
|
20
|
+
alias_method :visit_struct_constructor, :visit_constructor
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|