dry-struct 1.3.0 → 1.7.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 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: []