dry-struct 1.3.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64b4b12493455f8f916b5708d7c2afdc2649099d3b7b8c41045b73517cce05f8
4
- data.tar.gz: 84989a15e01c83c1d71b0d5e034ca54d7727fc7c37db29c0e8b2db88ece51f15
3
+ metadata.gz: fd8d3f0f41192daff14ed892da9e9a86e3dddefc4f7f94f26f58e1d091257532
4
+ data.tar.gz: 88d94467b25ec8f3a0e6f897232829ffbfb1ee3a1c1ec331e60ce3d5c386cb72
5
5
  SHA512:
6
- metadata.gz: 2462f24f972cbaa0c89399f76e3247c3b1fac9a6a88a0f1ea3a6e4ceb37fea2a463b58967c26b102456d589755fe2d3350c324b0646b870db27c6c434eba5acf
7
- data.tar.gz: 6156aa3704d9afffedc1370bf17371177d41696c289005fe5d41e4d356049ab881a6e40675756ab6bacd59ca4ac41f792a0dc872da9ffd2724b12cdd44c78e28
6
+ metadata.gz: a3d3a0a161d2a81053e7081a1d2deeead9a565c64a432350145c79083c58e400a8ed984e70b06388128c615d335cbccb29cb43b48f2669830bbf238064965c9e
7
+ data.tar.gz: 4c6a1a4c1a47f7b941e33c10ad515ce96247bae7e916088863a56c8c1104de5e4344b7314150eeb774627778c76fb79b6f209ba554627786a02ec918f7cf43df
data/CHANGELOG.md CHANGED
@@ -1,9 +1,87 @@
1
+ <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
+
3
+ ## 1.7.0 2025-01-06
4
+
5
+
6
+ ### Fixed
7
+
8
+ - Fixed coercion errors for structs (issue #192 via #193) (@flash-gordon)
9
+ - Invalid method names are now allowed as struct attributes (issue #169 via #195) (@flash-gordon)
10
+
11
+ ### Changed
12
+
13
+ - Missing attribute error now includes the name of the class (issue #170 via #191) (@phillipoertel + @cllns)
14
+ - 3.1 is now the minimum Ruby version (@flash-gordon)
15
+ - `Dry::Struct::Error` is now a subclass of `Dry::Types::CoercionError` (in #193) (@flash-gordon)
16
+ - `Dry::Struct#[]` now returns `nil` if an optional attribute is not set. This is consistent with calling accessor methods for optional attributes. (issue #171 via #194) (@ivleonov + @flash-gordon)
17
+
18
+ [Compare v1.6.0...v1.7.0](https://github.com/dry-rb/dry-struct/compare/v1.6.0...v1.7.0)
19
+
20
+ ## 1.6.0 2022-11-04
21
+
22
+
23
+ ### Changed
24
+
25
+ - This version uses dry-core 1.0 (@flash-gordon + @solnic)
26
+
27
+ [Compare v1.5.2...v1.6.0](https://github.com/dry-rb/dry-struct/compare/v1.5.2...v1.6.0)
28
+
29
+ ## 1.5.2 2022-10-19
30
+
31
+
32
+ ### Fixed
33
+
34
+ - Coercion failures keep the original error instead of just having a string (@flash-gordon + @newx)
35
+
36
+
37
+ [Compare v1.5.1...v1.5.2](https://github.com/dry-rb/dry-struct/compare/v1.5.1...v1.5.2)
38
+
39
+ ## 1.5.1 2022-10-17
40
+
41
+
42
+ ### Fixed
43
+
44
+ - Fixed issues with auto-loading `Extensions` module (issue #183 fixed via #184) (@solnic)
45
+
46
+
47
+ [Compare v1.5.0...v1.5.1](https://github.com/dry-rb/dry-struct/compare/v1.5.0...v1.5.1)
48
+
49
+ ## 1.5.0 2022-10-15
50
+
51
+
52
+ ### Changed
53
+
54
+ - Use zeitwerk for auto-loading (@flash-gordon)
55
+
56
+ [Compare v1.4.0...v1.5.0](https://github.com/dry-rb/dry-struct/compare/v1.4.0...v1.5.0)
57
+
58
+ ## 1.4.0 2021-01-21
59
+
60
+
61
+ ### Added
62
+
63
+ - Support for wrapping constructors and fallbacks, see release notes for dry-types 1.5.0 (@flash-gordon)
64
+ - Improvements of the attribute DSL, now it's possible to use optional structs as a base class (@flash-gordon)
65
+ ```ruby
66
+ class User < Dry::Struct
67
+ attribute :name, Types::String
68
+ attribute :address, Dry::Struct.optional do
69
+ attribute :city, Types::String
70
+ end
71
+ end
72
+
73
+ User.new(name: "John", address: nil) # => #<User name="John" address=nil>
74
+ ```
75
+
76
+
77
+ [Compare v1.3.0...v1.4.0](https://github.com/dry-rb/dry-struct/compare/v1.3.0...v1.4.0)
78
+
1
79
  ## 1.3.0 2020-02-10
2
80
 
3
81
 
4
82
  ### Added
5
83
 
6
- - Nested structures will reuse type and key transformations from of the enclosing struct (flash-gordon)
84
+ - Nested structures will reuse type and key transformations from the enclosing struct (@flash-gordon)
7
85
 
8
86
  ```ruby
9
87
  class User < Dry::Struct
@@ -22,13 +100,32 @@
22
100
  end
23
101
  end
24
102
  ```
25
- - `Dry::Struct::Constructor` finally acts like a fully-featured type (flash-gordon)
26
- - `Dry::Struct.abstract` declares a struct class as abstract. An abstract class is used as a default superclass for nested structs (flash-gordon)
27
- - `Struct.to_ast` and struct compiler (@flash-gordon)
103
+ - `Dry::Struct::Constructor` finally acts like a fully-featured type (@flash-gordon)
104
+ - `Dry::Struct.abstract` declares a struct class as abstract. An abstract class is used as a default superclass for nested structs (@flash-gordon)
105
+ - `Dry::Struct.to_ast` and struct compiler (@flash-gordon)
106
+ - Struct composition with `Dry::Struct.attributes_from`. It's more flexible than inheritance (@waiting-for-dev + @flash-gordon)
107
+
108
+ ```ruby
109
+ class Address < Dry::Struct
110
+ attribute :city, Types::String
111
+ attribute :zipcode, Types::String
112
+ end
113
+
114
+ class Buyer < Dry::Struct
115
+ attribute :name, Types::String
116
+ attributes_from Address
117
+ end
118
+
119
+ class Seller < Dry::Struct
120
+ attribute :name, Types::String
121
+ attribute :email, Types::String
122
+ attributes_from Address
123
+ end
124
+ ```
28
125
 
29
126
  ### Changed
30
127
 
31
- - [internal] metadata is now stored inside schema (flash-gordon)
128
+ - [internal] metadata is now stored inside schema (@flash-gordon)
32
129
 
33
130
  [Compare v1.2.0...v1.3.0](https://github.com/dry-rb/dry-struct/compare/v1.2.0...v1.3.0)
34
131
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2020 dry-rb team
3
+ Copyright (c) 2015-2023 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,28 +1,21 @@
1
+ <!--- this file is synced from dry-rb/template-gem project -->
1
2
  [gem]: https://rubygems.org/gems/dry-struct
2
3
  [actions]: https://github.com/dry-rb/dry-struct/actions
3
- [codacy]: https://www.codacy.com/gh/dry-rb/dry-struct
4
- [chat]: https://dry-rb.zulipchat.com
5
- [inchpages]: http://inch-ci.org/github/dry-rb/dry-struct
6
4
 
7
- # dry-struct [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
-
9
- [![Gem Version](https://badge.fury.io/rb/dry-struct.svg)][gem]
10
- [![CI Status](https://github.com/dry-rb/dry-struct/workflows/ci/badge.svg)][actions]
11
- [![Codacy Badge](https://api.codacy.com/project/badge/Grade/961f5c776f1d49218b2cede3745e059c)][codacy]
12
- [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/961f5c776f1d49218b2cede3745e059c)][codacy]
13
- [![Inline docs](http://inch-ci.org/github/dry-rb/dry-struct.svg?branch=master)][inchpages]
5
+ # dry-struct [![Gem Version](https://badge.fury.io/rb/dry-struct.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-struct/workflows/CI/badge.svg)][actions]
14
6
 
15
7
  ## Links
16
8
 
17
- * [User documentation](http://dry-rb.org/gems/dry-struct)
9
+ * [User documentation](https://dry-rb.org/gems/dry-struct)
18
10
  * [API documentation](http://rubydoc.info/gems/dry-struct)
11
+ * [Forum](https://discourse.dry-rb.org)
19
12
 
20
13
  ## Supported Ruby versions
21
14
 
22
15
  This library officially supports the following Ruby versions:
23
16
 
24
- * MRI >= `2.4`
25
- * jruby >= `9.2`
17
+ * MRI `>= 3.1`
18
+ * jruby `>= 9.4` (not tested on CI)
26
19
 
27
20
  ## License
28
21
 
data/dry-struct.gemspec CHANGED
@@ -1,40 +1,37 @@
1
1
  # frozen_string_literal: true
2
- # this file is managed by dry-rb/devtools project
3
2
 
4
- lib = File.expand_path('lib', __dir__)
3
+ # this file is synced from dry-rb/template-gem project
4
+
5
+ lib = File.expand_path("lib", __dir__)
5
6
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require 'dry/struct/version'
7
+ require "dry/struct/version"
7
8
 
8
9
  Gem::Specification.new do |spec|
9
- spec.name = 'dry-struct'
10
+ spec.name = "dry-struct"
10
11
  spec.authors = ["Piotr Solnica"]
11
12
  spec.email = ["piotr.solnica@gmail.com"]
12
- spec.license = 'MIT'
13
+ spec.license = "MIT"
13
14
  spec.version = Dry::Struct::VERSION.dup
14
15
 
15
16
  spec.summary = "Typed structs and value objects"
16
17
  spec.description = spec.summary
17
- spec.homepage = 'https://dry-rb.org/gems/dry-struct'
18
+ spec.homepage = "https://dry-rb.org/gems/dry-struct"
18
19
  spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-struct.gemspec", "lib/**/*"]
19
- spec.bindir = 'bin'
20
+ spec.bindir = "bin"
20
21
  spec.executables = []
21
- spec.require_paths = ['lib']
22
+ spec.require_paths = ["lib"]
22
23
 
23
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
- spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-struct/blob/master/CHANGELOG.md'
25
- spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-struct'
26
- spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-struct/issues'
24
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-struct/blob/main/CHANGELOG.md"
26
+ spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-struct"
27
+ spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-struct/issues"
28
+ spec.metadata["rubygems_mfa_required"] = "true"
27
29
 
28
- spec.required_ruby_version = ">= 2.4.0"
30
+ spec.required_ruby_version = ">= 3.1.0"
29
31
 
30
32
  # to update dependencies edit project.yml
31
- spec.add_runtime_dependency "dry-core", "~> 0.4", ">= 0.4.4"
32
- spec.add_runtime_dependency "dry-equalizer", "~> 0.3"
33
- spec.add_runtime_dependency "dry-types", "~> 1.3"
34
- spec.add_runtime_dependency "ice_nine", "~> 0.11"
35
-
36
- spec.add_development_dependency "bundler"
37
- spec.add_development_dependency "rake"
38
- spec.add_development_dependency "rspec"
39
- spec.add_development_dependency "yard"
33
+ spec.add_dependency "dry-core", "~> 1.1"
34
+ spec.add_dependency "dry-types", "~> 1.8"
35
+ spec.add_dependency "ice_nine", "~> 0.11"
36
+ spec.add_dependency "zeitwerk", "~> 2.6"
40
37
  end
@@ -1,32 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'weakref'
4
- require 'dry/core/class_attributes'
5
- require 'dry/core/inflector'
6
- require 'dry/core/descendants_tracker'
7
-
8
- require 'dry/struct/errors'
9
- require 'dry/struct/constructor'
10
- require 'dry/struct/sum'
3
+ require "weakref"
11
4
 
12
5
  module Dry
13
6
  class Struct
14
7
  # Class-level interface of {Struct} and {Value}
15
- module ClassInterface
8
+ module ClassInterface # rubocop:disable Metrics/ModuleLength
16
9
  include Core::ClassAttributes
17
10
 
18
11
  include Types::Type
19
12
  include Types::Builder
20
13
 
21
- # @param [Class] klass
22
- def inherited(klass)
23
- super
24
-
25
- unless klass.name.eql?('Dry::Struct::Value')
26
- klass.extend(Core::DescendantsTracker)
27
- end
28
- end
29
-
30
14
  # Adds an attribute for this {Struct} with given `name` and `type`
31
15
  # and modifies {.schema} accordingly.
32
16
  #
@@ -49,8 +33,12 @@ module Dry
49
33
  # end
50
34
  # end
51
35
  #
52
- # Language.schema
53
- # # => #<Dry::Types[Constructor<Schema<keys={name: Constrained<Nominal<String> rule=[type?(String)]> details: Language::Details}> fn=Kernel.Hash>]>
36
+ # Language.schema # new lines for readability
37
+ # # => #<Dry::Types[
38
+ # Constructor<Schema<keys={
39
+ # name: Constrained<Nominal<String> rule=[type?(String)]>
40
+ # details: Language::Details
41
+ # }> fn=Kernel.Hash>]>
54
42
  #
55
43
  # ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
56
44
  # ruby.name #=> 'Ruby'
@@ -67,10 +55,12 @@ module Dry
67
55
  # end
68
56
  # end
69
57
  #
70
- # Language.schema
58
+ # Language.schema # new lines for readability
71
59
  # => #<Dry::Types[Constructor<Schema<keys={
72
60
  # name: Constrained<Nominal<String> rule=[type?(String)]>
73
- # versions: Constrained<Array<Constrained<Nominal<String> rule=[type?(String)]>> rule=[type?(Array)]>
61
+ # versions: Constrained<
62
+ # Array<Constrained<Nominal<String> rule=[type?(String)]>
63
+ # > rule=[type?(Array)]>
74
64
  # celebrities: Constrained<Array<Language::Celebrity> rule=[type?(Array)]>
75
65
  # }> fn=Kernel.Hash>]>
76
66
  #
@@ -93,8 +83,8 @@ module Dry
93
83
  # ruby.celebrities[0].pseudonym #=> 'Matz'
94
84
  # ruby.celebrities[1].name #=> 'Aaron Patterson'
95
85
  # ruby.celebrities[1].pseudonym #=> 'tenderlove'
96
- def attribute(name, type = Undefined, &block)
97
- attributes(name => build_type(name, type, &block))
86
+ def attribute(name, type = Undefined, &)
87
+ attributes(name => build_type(name, type, &))
98
88
  end
99
89
 
100
90
  # Add atributes from another struct
@@ -122,13 +112,13 @@ module Dry
122
112
  #
123
113
  # @param struct [Dry::Struct]
124
114
  def attributes_from(struct)
125
- extracted_schema = struct.schema.keys.map { |key|
115
+ extracted_schema = struct.schema.keys.to_h do |key|
126
116
  if key.required?
127
117
  [key.name, key.type]
128
118
  else
129
119
  [:"#{key.name}?", key.type]
130
120
  end
131
- }.to_h
121
+ end
132
122
  attributes(extracted_schema)
133
123
  end
134
124
 
@@ -146,19 +136,19 @@ module Dry
146
136
  # @param [Dry::Types::Type, nil] type or superclass of nested type
147
137
  # @return [Dry::Struct]
148
138
  #
149
- def attribute?(*args, &block)
150
- if args.size == 1 && block.nil?
139
+ def attribute?(*args, &)
140
+ if args.size == 1 && !block_given?
151
141
  Core::Deprecations.warn(
152
- 'Dry::Struct.attribute? is deprecated for checking attribute presence, '\
153
- 'use has_attribute? instead',
154
- tag: :'dry-struct'
142
+ "Dry::Struct.attribute? is deprecated for checking attribute presence, " \
143
+ "use has_attribute? instead",
144
+ tag: :"dry-struct"
155
145
  )
156
146
 
157
147
  has_attribute?(args[0])
158
148
  else
159
149
  name, * = args
160
150
 
161
- attribute(:"#{name}?", build_type(*args, &block))
151
+ attribute(:"#{name}?", build_type(*args, &))
162
152
  end
163
153
  end
164
154
 
@@ -181,7 +171,7 @@ module Dry
181
171
  # # author: Constrained<Nominal<String> rule=[type?(String)]>
182
172
  # # }> fn=Kernel.Hash>]>
183
173
  def attributes(new_schema)
184
- keys = new_schema.keys.map { |k| k.to_s.chomp('?').to_sym }
174
+ keys = new_schema.keys.map { |k| k.to_s.chomp("?").to_sym }
185
175
  check_schema_duplication(keys)
186
176
 
187
177
  schema schema.schema(new_schema)
@@ -190,9 +180,8 @@ module Dry
190
180
 
191
181
  @attribute_names = nil
192
182
 
193
- direct_descendants = descendants.select { |d| d.superclass == self }
194
- direct_descendants.each do |d|
195
- inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp('?').to_sym) }
183
+ subclasses.each do |d|
184
+ inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp("?").to_sym) }
196
185
  d.attributes(inherited_attrs)
197
186
  end
198
187
 
@@ -233,7 +222,7 @@ module Dry
233
222
  schema schema.with_key_transform(proc || block)
234
223
  end
235
224
 
236
- # @param [Hash{Symbol => Dry::Types::Type, Dry::Struct}] new_schema
225
+ # @param [Hash{Symbol => Dry::Types::Type, Dry::Struct}] new_keys
237
226
  # @raise [RepeatedAttributeError] when trying to define attribute with the
238
227
  # same name as previously defined one
239
228
  def check_schema_duplication(new_keys)
@@ -247,7 +236,7 @@ module Dry
247
236
 
248
237
  # @param [Hash{Symbol => Object},Dry::Struct] attributes
249
238
  # @raise [Struct::Error] if the given attributes don't conform {#schema}
250
- def new(attributes = default_attributes, safe = false, &block)
239
+ def new(attributes = default_attributes, safe = false, &) # rubocop:disable Style/OptionalBooleanParameter
251
240
  if attributes.is_a?(Struct)
252
241
  if equal?(attributes.class)
253
242
  attributes
@@ -257,23 +246,23 @@ module Dry
257
246
  # User.new(super_user)
258
247
  #
259
248
  # We may deprecate this behavior in future forcing people to be explicit
260
- new(attributes.to_h, safe, &block)
249
+ new(attributes.to_h, safe, &)
261
250
  end
262
251
  elsif safe
263
252
  load(schema.call_safe(attributes) { |output = attributes| return yield output })
264
253
  else
265
254
  load(schema.call_unsafe(attributes))
266
255
  end
267
- rescue Types::CoercionError => error
268
- raise Error, "[#{self}.new] #{error}", error.backtrace
256
+ rescue Types::CoercionError => e
257
+ raise Error, "[#{self}.new] #{e}", e.backtrace
269
258
  end
270
259
 
271
260
  # @api private
272
- def call_safe(input, &block)
261
+ def call_safe(input, &)
273
262
  if input.is_a?(self)
274
263
  input
275
264
  else
276
- new(input, true, &block)
265
+ new(input, true, &)
277
266
  end
278
267
  end
279
268
 
@@ -289,7 +278,7 @@ module Dry
289
278
  # @api private
290
279
  def load(attributes)
291
280
  struct = allocate
292
- struct.send(:initialize, attributes)
281
+ struct.__send__(:initialize, attributes)
293
282
  struct
294
283
  end
295
284
 
@@ -297,17 +286,17 @@ module Dry
297
286
  # @param [#call,nil] block
298
287
  # @return [Dry::Struct::Constructor]
299
288
  def constructor(constructor = nil, **, &block)
300
- Constructor.new(self, fn: constructor || block)
289
+ Constructor[self, fn: constructor || block]
301
290
  end
302
291
 
303
292
  # @param [Hash{Symbol => Object},Dry::Struct] input
304
293
  # @yieldparam [Dry::Types::Result::Failure] failure
305
- # @yieldreturn [Dry::Types::ResultResult]
294
+ # @yieldreturn [Dry::Types::Result]
306
295
  # @return [Dry::Types::Result]
307
296
  def try(input)
308
297
  success(self[input])
309
298
  rescue Error => e
310
- failure_result = failure(input, e.message)
299
+ failure_result = failure(input, e)
311
300
  block_given? ? yield(failure_result) : failure_result
312
301
  end
313
302
 
@@ -324,48 +313,32 @@ module Dry
324
313
 
325
314
  # @param [({Symbol => Object})] args
326
315
  # @return [Dry::Types::Result::Success]
327
- def success(*args)
328
- result(Types::Result::Success, *args)
329
- end
316
+ def success(*args) = result(Types::Result::Success, *args)
330
317
 
331
318
  # @param [({Symbol => Object})] args
332
319
  # @return [Dry::Types::Result::Failure]
333
- def failure(*args)
334
- result(Types::Result::Failure, *args)
335
- end
320
+ def failure(*args) = result(Types::Result::Failure, *args)
336
321
 
337
322
  # @param [Class] klass
338
323
  # @param [({Symbol => Object})] args
339
- def result(klass, *args)
340
- klass.new(*args)
341
- end
324
+ def result(klass, *args) = klass.new(*args)
342
325
 
343
326
  # @return [false]
344
- def default?
345
- false
346
- end
327
+ def default? = false
347
328
 
348
- # @param [Object, Dry::Struct] value
329
+ # @param [Object, Dry::Struct] other
349
330
  # @return [Boolean]
350
- def ===(other)
351
- other.is_a?(self)
352
- end
331
+ def ===(other) = other.is_a?(self)
353
332
  alias_method :primitive?, :===
354
333
 
355
334
  # @return [true]
356
- def constrained?
357
- true
358
- end
335
+ def constrained? = true
359
336
 
360
337
  # @return [self]
361
- def primitive
362
- self
363
- end
338
+ def primitive = self
364
339
 
365
340
  # @return [false]
366
- def optional?
367
- false
368
- end
341
+ def optional? = false
369
342
 
370
343
  # @return [Proc]
371
344
  def to_proc
@@ -376,9 +349,7 @@ module Dry
376
349
  #
377
350
  # @param [Symbol] key Attribute name
378
351
  # @return [Boolean]
379
- def has_attribute?(key)
380
- schema.key?(key)
381
- end
352
+ def has_attribute?(key) = schema.key?(key)
382
353
 
383
354
  # Gets the list of attribute names
384
355
  #
@@ -456,39 +427,45 @@ module Dry
456
427
  # Constructs a type
457
428
  #
458
429
  # @return [Dry::Types::Type, Dry::Struct]
459
- def build_type(name, type = Undefined, &block)
430
+ def build_type(name, type = Undefined, &)
460
431
  type_object =
461
432
  if type.is_a?(::String)
462
433
  Types[type]
463
- elsif block.nil? && Undefined.equal?(type)
434
+ elsif !block_given? && Undefined.equal?(type)
464
435
  raise(
465
436
  ::ArgumentError,
466
- 'you must supply a type or a block to `Dry::Struct.attribute`'
437
+ "you must supply a type or a block to `Dry::Struct.attribute`"
467
438
  )
468
439
  else
469
440
  type
470
441
  end
471
442
 
472
- if block
473
- struct_builder.(name, type_object, &block)
443
+ if block_given?
444
+ struct_builder.(name, type_object, &)
474
445
  else
475
446
  type_object
476
447
  end
477
448
  end
478
449
  private :build_type
479
450
 
451
+ # @api private
480
452
  def define_accessors(keys)
481
- keys.each do |key|
482
- next if instance_methods.include?(key)
483
-
484
- class_eval(<<-RUBY)
485
- def #{key}
486
- @attributes[#{key.inspect}]
487
- end
488
- RUBY
453
+ (keys - instance_methods).each do |key|
454
+ if valid_method_name?(key)
455
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
456
+ def #{key} # def email
457
+ @attributes[#{key.inspect}] # @attributes[:email]
458
+ end # end
459
+ RUBY
460
+ else
461
+ define_method(key) { @attributes[key] }
462
+ end
489
463
  end
490
464
  end
491
465
  private :define_accessors
466
+
467
+ # @api private
468
+ private def valid_method_name?(key) = key.to_s.match?(/\A[a-zA-Z_]\w*\z/)
492
469
  end
493
470
  end
494
471
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'weakref'
4
- require 'dry/types/compiler'
5
-
6
3
  module Dry
7
4
  class Struct
8
5
  class Compiler < Types::Compiler
@@ -3,7 +3,7 @@
3
3
  module Dry
4
4
  class Struct
5
5
  # Raised when given input doesn't conform schema and constructor type
6
- Error = Class.new(TypeError)
6
+ Error = Class.new(::Dry::Types::CoercionError)
7
7
 
8
8
  # Raised when defining duplicate attributes
9
9
  class RepeatedAttributeError < ::ArgumentError
@@ -16,8 +16,8 @@ module Dry
16
16
 
17
17
  # Raised when a struct doesn't have an attribute
18
18
  class MissingAttributeError < ::KeyError
19
- def initialize(key)
20
- super("Missing attribute: #{key.inspect}")
19
+ def initialize(attribute:, klass:)
20
+ super("Missing attribute: #{attribute.inspect} on #{klass}")
21
21
  end
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ module Dry
25
25
  # This shouldn't happen in a working application
26
26
  class RecycledStructError < ::RuntimeError
27
27
  def initialize
28
- super('Reference to struct class was garbage collected')
28
+ super("Reference to struct class was garbage collected")
29
29
  end
30
30
  end
31
31
  end
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pp'
3
+ require "pp"
4
4
 
5
5
  module Dry
6
6
  class Struct
7
7
  def pretty_print(pp)
8
8
  klass = self.class
9
- pp.group(1, "#<#{klass.name || klass.inspect}", '>') do
10
- pp.seplist(@attributes.keys, proc { pp.text ',' }) do |column_name|
9
+ pp.group(1, "#<#{klass.name || klass.inspect}", ">") do
10
+ pp.seplist(@attributes.keys, proc { pp.text "," }) do |column_name|
11
11
  column_value = @attributes[column_name]
12
- pp.breakable ' '
12
+ pp.breakable " "
13
13
  pp.group(1) do
14
- pp.text column_name
15
- pp.text '='
14
+ pp.text column_name.to_s
15
+ pp.text "="
16
16
  pp.pp column_value
17
17
  end
18
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Dry::Struct.register_extension(:pretty_print) do
4
- require 'dry/struct/extensions/pretty_print'
4
+ require "dry/struct/extensions/pretty_print"
5
5
  end
@@ -9,11 +9,11 @@ module Dry
9
9
  # @return [Hash, Array]
10
10
  def self.[](value)
11
11
  if value.is_a?(Struct)
12
- value.to_h.transform_values { |current| self[current] }
12
+ value.to_h.transform_values { self[_1] }
13
13
  elsif value.respond_to?(:to_hash)
14
- value.to_hash.transform_values { |current| self[current] }
14
+ value.to_hash.transform_values { self[_1] }
15
15
  elsif value.respond_to?(:to_ary)
16
- value.to_ary.map { |item| self[item] }
16
+ value.to_ary.map { self[_1] }
17
17
  else
18
18
  value
19
19
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/printer'
3
+ require "dry/types/printer"
4
4
 
5
5
  module Dry
6
6
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/struct/compiler'
4
-
5
3
  module Dry
6
4
  class Struct
7
5
  # @private
@@ -14,9 +12,10 @@ module Dry
14
12
  end
15
13
 
16
14
  # @param [Symbol|String] attr_name the name of the nested type
17
- # @param [Dry::Struct,Dry::Types::Type::Array,Undefined] type the superclass of the nested struct
15
+ # @param [Dry::Struct,Dry::Types::Type::Array,Undefined] type the superclass
16
+ # of the nested struct
18
17
  # @yield the body of the nested struct
19
- def call(attr_name, type, &block)
18
+ def call(attr_name, type, &)
20
19
  const_name = const_name(type, attr_name)
21
20
  check_name(const_name)
22
21
 
@@ -28,12 +27,15 @@ module Dry
28
27
  schema builder.struct.schema.clear
29
28
  end
30
29
 
31
- class_exec(&block)
30
+ class_exec(&)
32
31
  end
32
+
33
33
  struct.const_set(const_name, new_type)
34
34
 
35
35
  if array?(type)
36
36
  type.of(new_type)
37
+ elsif optional?(type)
38
+ new_type.optional
37
39
  else
38
40
  new_type
39
41
  end
@@ -41,13 +43,19 @@ module Dry
41
43
 
42
44
  private
43
45
 
46
+ def type?(type) = type.is_a?(Types::Type)
47
+
44
48
  def array?(type)
45
- type.is_a?(Types::Type) && type.primitive.equal?(::Array)
49
+ type?(type) && !type.optional? && type.primitive.equal?(::Array)
46
50
  end
47
51
 
52
+ def optional?(type) = type?(type) && type.optional?
53
+
48
54
  def parent(type)
49
55
  if array?(type)
50
56
  visit(type.to_ast)
57
+ elsif optional?(type)
58
+ type.right
51
59
  else
52
60
  type
53
61
  end
@@ -65,10 +73,12 @@ module Dry
65
73
  end
66
74
 
67
75
  def check_name(name)
68
- raise(
69
- Error,
70
- "Can't create nested attribute - `#{struct}::#{name}` already defined"
71
- ) if struct.const_defined?(name, false)
76
+ if struct.const_defined?(name, false)
77
+ raise(
78
+ Error,
79
+ "Can't create nested attribute - `#{struct}::#{name}` already defined"
80
+ )
81
+ end
72
82
  end
73
83
 
74
84
  def visit_constrained(node)
@@ -81,9 +91,7 @@ module Dry
81
91
  visit(member)
82
92
  end
83
93
 
84
- def visit_nominal(*)
85
- Undefined
86
- end
94
+ def visit_nominal(*) = Undefined
87
95
 
88
96
  def visit_constructor(node)
89
97
  definition, * = node
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/types/sum'
4
- require 'dry/types/printer'
5
-
6
3
  module Dry
7
4
  class Struct
8
5
  # A sum type of two or more structs
@@ -17,11 +14,11 @@ module Dry
17
14
 
18
15
  # @param [Hash{Symbol => Object},Dry::Struct] input
19
16
  # @yieldparam [Dry::Types::Result::Failure] failure
20
- # @yieldreturn [Dry::Types::ResultResult]
17
+ # @yieldreturn [Dry::Types::Result]
21
18
  # @return [Dry::Types::Result]
22
19
  def try(input)
23
20
  if input.is_a?(Struct)
24
- try_struct(input) { super }
21
+ ::Dry::Types::Result::Success.new(try_struct(input) { return super })
25
22
  else
26
23
  super
27
24
  end
@@ -31,7 +28,7 @@ module Dry
31
28
  # @param [Dry::Types::Type] type
32
29
  # @return [Dry::Types::Sum]
33
30
  def |(type)
34
- if type.is_a?(Class) && type <= Struct || type.is_a?(Sum)
31
+ if (type.is_a?(::Class) && type <= Struct) || type.is_a?(Sum)
35
32
  Sum.new(self, type)
36
33
  else
37
34
  super
@@ -39,16 +36,14 @@ module Dry
39
36
  end
40
37
 
41
38
  # @return [boolean]
42
- def ===(value)
43
- left === value || right === value
44
- end
39
+ def ===(value) = left === value || right === value
45
40
 
46
41
  protected
47
42
 
48
43
  # @private
49
- def try_struct(input)
44
+ def try_struct(input, &)
50
45
  left.try_struct(input) do
51
- right.try_struct(input) { yield }
46
+ right.try_struct(input, &)
52
47
  end
53
48
  end
54
49
  end
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ice_nine'
4
- require 'dry/core/deprecations'
3
+ require "ice_nine"
5
4
 
6
5
  module Dry
7
6
  class Struct
8
- extend Core::Deprecations[:'dry-struct']
7
+ extend Core::Deprecations[:"dry-struct"]
9
8
 
10
9
  # {Value} objects behave like {Struct}s but *deeply frozen*
11
10
  # using [`ice_nine`](https://github.com/dkubb/ice_nine)
@@ -30,9 +29,7 @@ module Dry
30
29
  # @param (see ClassInterface#new)
31
30
  # @return [Value]
32
31
  # @see https://github.com/dkubb/ice_nine
33
- def self.new(*)
34
- ::IceNine.deep_freeze(super)
35
- end
32
+ def self.new(*) = ::IceNine.deep_freeze(super)
36
33
  end
37
34
 
38
35
  deprecate_constant :Value
@@ -3,6 +3,6 @@
3
3
  module Dry
4
4
  class Struct
5
5
  # @private
6
- VERSION = '1.3.0'.freeze
6
+ VERSION = "1.7.0"
7
7
  end
8
8
  end
data/lib/dry/struct.rb CHANGED
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
- require 'dry-equalizer'
5
- require 'dry/core/extensions'
6
- require 'dry/core/constants'
7
- require 'dry/core/deprecations'
8
-
9
- require 'dry/struct/version'
10
- require 'dry/struct/errors'
11
- require 'dry/struct/class_interface'
12
- require 'dry/struct/hashify'
13
- require 'dry/struct/struct_builder'
3
+ require "weakref"
4
+
5
+ require "dry/core"
6
+ require "dry/types"
7
+
8
+ require "dry/struct/class_interface"
9
+ require "dry/struct/errors"
10
+ require "dry/struct/version"
14
11
 
15
12
  module Dry
16
13
  # Constructor method for easily creating a {Dry::Struct}.
@@ -30,10 +27,10 @@ module Dry
30
27
  # Test = Dry.Struct(expected: Types::String) { schema(schema.strict) }
31
28
  # Test[expected: "foo", unexpected: "bar"]
32
29
  # #=> Dry::Struct::Error: [Test.new] unexpected keys [:unexpected] in Hash input
33
- def self.Struct(attributes = Dry::Core::Constants::EMPTY_HASH, &block)
30
+ def self.Struct(attributes = Dry::Core::Constants::EMPTY_HASH, &)
34
31
  Class.new(Dry::Struct) do
35
32
  attributes.each { |a, type| attribute a, type }
36
- module_eval(&block) if block
33
+ module_eval(&) if block_given?
37
34
  end
38
35
  end
39
36
 
@@ -88,21 +85,35 @@ module Dry
88
85
  extend Core::Extensions
89
86
  include Core::Constants
90
87
  extend ClassInterface
91
- extend Core::Deprecations[:'dry-struct']
88
+ extend Core::Deprecations[:"dry-struct"]
92
89
 
93
90
  class << self
94
91
  # override `Dry::Types::Builder#prepend`
95
92
  define_method(:prepend, ::Module.method(:prepend))
93
+
94
+ def loader
95
+ @loader ||= ::Zeitwerk::Loader.new.tap do |loader|
96
+ root = ::File.expand_path("..", __dir__)
97
+ loader.tag = "dry-struct"
98
+ loader.inflector = ::Zeitwerk::GemInflector.new("#{root}/dry-struct.rb")
99
+ loader.push_dir(root)
100
+ loader.ignore(
101
+ "#{root}/dry-struct.rb",
102
+ "#{root}/dry/struct/{class_interface,errors,extensions,printer,value,version}.rb",
103
+ "#{root}/dry/struct/extensions"
104
+ )
105
+ end
106
+ end
96
107
  end
97
108
 
98
- autoload :Value, 'dry/struct/value'
109
+ loader.setup
99
110
 
100
111
  include ::Dry::Equalizer(:__attributes__, inspect: false, immutable: true)
101
112
 
102
113
  # {Dry::Types::Hash::Schema} subclass with specific behaviour defined for
103
114
  # @return [Dry::Types::Hash::Schema]
104
115
  defines :schema
105
- schema Types['coercible.hash'].schema(EMPTY_HASH)
116
+ schema Types["coercible.hash"].schema(EMPTY_HASH)
106
117
 
107
118
  defines :abstract_class
108
119
  abstract
@@ -134,7 +145,13 @@ module Dry
134
145
  # rom_n_roda[:title] #=> 'Web Development with ROM and Roda'
135
146
  # rom_n_roda[:subtitle] #=> nil
136
147
  def [](name)
137
- @attributes.fetch(name) { raise MissingAttributeError.new(name) }
148
+ @attributes.fetch(name) do
149
+ if self.class.attribute_names.include?(name)
150
+ nil
151
+ else
152
+ raise MissingAttributeError.new(attribute: name, klass: self.class)
153
+ end
154
+ end
138
155
  end
139
156
 
140
157
  # Converts the {Dry::Struct} to a hash with keys representing
@@ -159,8 +176,8 @@ module Dry
159
176
  result[key.name] = Hashify[self[key.name]] if attributes.key?(key.name)
160
177
  end
161
178
  end
179
+ # TODO: remove in 2.0
162
180
  alias_method :to_hash, :to_h
163
- # deprecate :to_hash, :to_h, message: "Implicit convertion structs to hashes is deprecated. Use .to_h"
164
181
 
165
182
  # Create a copy of {Dry::Struct} with overriden attributes
166
183
  #
@@ -183,10 +200,14 @@ module Dry
183
200
  # rom_n_roda.new(subtitle: '3rd edition')
184
201
  # #=> #<Book title="Web Development with ROM and Roda" subtitle="3rd edition">
185
202
  def new(changeset)
186
- new_attributes = self.class.schema.apply(changeset, skip_missing: true, resolve_defaults: false)
203
+ new_attributes = self.class.schema.apply(
204
+ changeset,
205
+ skip_missing: true,
206
+ resolve_defaults: false
207
+ )
187
208
  self.class.load(__attributes__.merge(new_attributes))
188
- rescue Types::SchemaError, Types::MissingKeyError, Types::UnknownKeysError => error
189
- raise Error, "[#{self}.new] #{error}"
209
+ rescue Types::SchemaError, Types::MissingKeyError, Types::UnknownKeysError => e
210
+ raise Error, "[#{self}.new] #{e}"
190
211
  end
191
212
  alias_method :__new__, :new
192
213
 
@@ -197,16 +218,13 @@ module Dry
197
218
  "#<#{klass.name || klass.inspect}#{attrs}>"
198
219
  end
199
220
 
200
- if RUBY_VERSION >= '2.7'
201
- # Pattern matching support
202
- #
203
- # @api private
204
- def deconstruct_keys(keys)
205
- attributes
206
- end
207
- end
221
+ # Pattern matching support
222
+ #
223
+ # @api private
224
+ def deconstruct_keys(_keys) = attributes
208
225
  end
209
226
  end
210
227
 
211
- require 'dry/struct/extensions'
212
- require 'dry/struct/printer'
228
+ require "dry/struct/extensions"
229
+ require "dry/struct/printer"
230
+ require "dry/struct/value"
data/lib/dry-struct.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/struct'
3
+ require "dry/struct"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-10 00:00:00.000000000 Z
11
+ date: 2025-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-core
@@ -16,48 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.4'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 0.4.4
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '0.4'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 0.4.4
33
- - !ruby/object:Gem::Dependency
34
- name: dry-equalizer
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '0.3'
19
+ version: '1.1'
40
20
  type: :runtime
41
21
  prerelease: false
42
22
  version_requirements: !ruby/object:Gem::Requirement
43
23
  requirements:
44
24
  - - "~>"
45
25
  - !ruby/object:Gem::Version
46
- version: '0.3'
26
+ version: '1.1'
47
27
  - !ruby/object:Gem::Dependency
48
28
  name: dry-types
49
29
  requirement: !ruby/object:Gem::Requirement
50
30
  requirements:
51
31
  - - "~>"
52
32
  - !ruby/object:Gem::Version
53
- version: '1.3'
33
+ version: '1.8'
54
34
  type: :runtime
55
35
  prerelease: false
56
36
  version_requirements: !ruby/object:Gem::Requirement
57
37
  requirements:
58
38
  - - "~>"
59
39
  - !ruby/object:Gem::Version
60
- version: '1.3'
40
+ version: '1.8'
61
41
  - !ruby/object:Gem::Dependency
62
42
  name: ice_nine
63
43
  requirement: !ruby/object:Gem::Requirement
@@ -73,61 +53,19 @@ dependencies:
73
53
  - !ruby/object:Gem::Version
74
54
  version: '0.11'
75
55
  - !ruby/object:Gem::Dependency
76
- name: bundler
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: '0'
89
- - !ruby/object:Gem::Dependency
90
- name: rake
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: '0'
103
- - !ruby/object:Gem::Dependency
104
- name: rspec
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
- - !ruby/object:Gem::Dependency
118
- name: yard
56
+ name: zeitwerk
119
57
  requirement: !ruby/object:Gem::Requirement
120
58
  requirements:
121
- - - ">="
59
+ - - "~>"
122
60
  - !ruby/object:Gem::Version
123
- version: '0'
124
- type: :development
61
+ version: '2.6'
62
+ type: :runtime
125
63
  prerelease: false
126
64
  version_requirements: !ruby/object:Gem::Requirement
127
65
  requirements:
128
- - - ">="
66
+ - - "~>"
129
67
  - !ruby/object:Gem::Version
130
- version: '0'
68
+ version: '2.6'
131
69
  description: Typed structs and value objects
132
70
  email:
133
71
  - piotr.solnica@gmail.com
@@ -158,10 +96,11 @@ licenses:
158
96
  - MIT
159
97
  metadata:
160
98
  allowed_push_host: https://rubygems.org
161
- changelog_uri: https://github.com/dry-rb/dry-struct/blob/master/CHANGELOG.md
99
+ changelog_uri: https://github.com/dry-rb/dry-struct/blob/main/CHANGELOG.md
162
100
  source_code_uri: https://github.com/dry-rb/dry-struct
163
101
  bug_tracker_uri: https://github.com/dry-rb/dry-struct/issues
164
- post_install_message:
102
+ rubygems_mfa_required: 'true'
103
+ post_install_message:
165
104
  rdoc_options: []
166
105
  require_paths:
167
106
  - lib
@@ -169,15 +108,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
169
108
  requirements:
170
109
  - - ">="
171
110
  - !ruby/object:Gem::Version
172
- version: 2.4.0
111
+ version: 3.1.0
173
112
  required_rubygems_version: !ruby/object:Gem::Requirement
174
113
  requirements:
175
114
  - - ">="
176
115
  - !ruby/object:Gem::Version
177
116
  version: '0'
178
117
  requirements: []
179
- rubygems_version: 3.0.3
180
- signing_key:
118
+ rubygems_version: 3.3.27
119
+ signing_key:
181
120
  specification_version: 4
182
121
  summary: Typed structs and value objects
183
122
  test_files: []