dry-struct 0.4.0 → 0.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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +7 -4
- data/CHANGELOG.md +114 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +3 -1
- data/README.md +0 -2
- data/bin/console +5 -7
- data/dry-struct.gemspec +4 -2
- data/lib/dry/struct/class_interface.rb +238 -39
- data/lib/dry/struct/errors.rb +7 -0
- data/lib/dry/struct/extensions/pretty_print.rb +20 -0
- data/lib/dry/struct/extensions.rb +3 -0
- data/lib/dry/struct/hashify.rb +7 -3
- data/lib/dry/struct/struct_builder.rb +86 -0
- data/lib/dry/struct/sum.rb +42 -0
- data/lib/dry/struct/version.rb +2 -1
- data/lib/dry/struct.rb +50 -129
- data/log/.gitkeep +0 -0
- metadata +14 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 33cd05dd51a3310d7ede685ae1ce8631a0955d7563afca7633a7625b9b34dd17
|
4
|
+
data.tar.gz: 182c58865ccb3e74595aa22d6c9730ce8ca0e078d1b6ecb5e8b69e026ce6409d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f5e4f6609b93e80ae775ab556812cc872883eaf423a74ae2e5191d2bef6907152653fa7fe441bb9aab945b7cf9f4ec62dd4d359ffcb88f4e976b8b53fb42ac8
|
7
|
+
data.tar.gz: 4b76e94533ff98f1c1d9d427967a5c19af3d58484a5e785c7eac15d50211d70e4718d35572bb6e89fbafeb9b9f092a73f68e517244676f28d4c7e4854f1a055a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -4,13 +4,16 @@ sudo: required
|
|
4
4
|
bundler_args: --without benchmarks tools
|
5
5
|
script:
|
6
6
|
- bundle exec rake spec
|
7
|
+
before_install:
|
8
|
+
- gem update --system
|
7
9
|
after_success:
|
8
10
|
- '[ -d coverage ] && bundle exec codeclimate-test-reporter'
|
9
11
|
rvm:
|
10
|
-
- 2.2.
|
11
|
-
- 2.3.
|
12
|
-
- 2.4.
|
13
|
-
-
|
12
|
+
- 2.2.10
|
13
|
+
- 2.3.7
|
14
|
+
- 2.4.4
|
15
|
+
- 2.5.1
|
16
|
+
- jruby-9.2.0.0
|
14
17
|
env:
|
15
18
|
global:
|
16
19
|
- COVERAGE=true
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,117 @@
|
|
1
|
+
# v0.6.0 2018-10-24
|
2
|
+
|
3
|
+
## BREAKING CHANGES
|
4
|
+
|
5
|
+
* `Struct.attribute?` in the old sense is deprecated, use `has_attribute?` as a replacement
|
6
|
+
|
7
|
+
## Added
|
8
|
+
|
9
|
+
* `Struct.attribute?` is an easy way to define omittable attributes (flash-gordon):
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class User < Dry::Struct
|
13
|
+
attribute :name, Types::Strict::String
|
14
|
+
attribute? :email, Types::Strict::String
|
15
|
+
end
|
16
|
+
# User.new(name: 'John') # => #<User name="John">
|
17
|
+
```
|
18
|
+
|
19
|
+
## Fixed
|
20
|
+
|
21
|
+
* `Struct#to_h` recursively converts hash values to hashes, this was done to be consistent with current behavior for arrays (oeoeaio + ZimbiX)
|
22
|
+
|
23
|
+
[Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-struct/compare/v0.5.1...v0.6.0)
|
24
|
+
|
25
|
+
# v0.5.1 2018-08-11
|
26
|
+
|
27
|
+
## Fixed
|
28
|
+
|
29
|
+
* Constant resolution is now restricted to the current module when structs are automatically defined using the block syntax. This shouldn't break any existing code (piktur)
|
30
|
+
|
31
|
+
## Added
|
32
|
+
|
33
|
+
* Pretty print extension (ojab)
|
34
|
+
```ruby
|
35
|
+
Dry::Struct.load_extensions(:pretty_print)
|
36
|
+
PP.pp(user)
|
37
|
+
#<Test::User
|
38
|
+
name="Jane",
|
39
|
+
age=21,
|
40
|
+
address=#<Test::Address city="NYC", zipcode="123">>
|
41
|
+
```
|
42
|
+
|
43
|
+
[Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-struct/compare/v0.5.0...v0.5.1)
|
44
|
+
|
45
|
+
# v0.5.0 2018-05-03
|
46
|
+
|
47
|
+
## BREAKING CHANGES
|
48
|
+
|
49
|
+
* `constructor_type` was removed, use `transform_types` and `transform_keys` as a replacement (see below)
|
50
|
+
* Default types are evaluated _only_ on missing values. Again, use `tranform_types` as a work around for `nil`s
|
51
|
+
* Values are now stored within a single instance variable names `@attributes`, this sped up struct creation and improved support for reserved attribute names such as `hash`, they don't get a getter but still can be read via `#[]`
|
52
|
+
* Ruby 2.3 is a minimal supported version
|
53
|
+
|
54
|
+
## Added
|
55
|
+
|
56
|
+
* `Dry::Struct.transform_types` accepts a block which is yielded on every type to add. Since types are `dry-types`' objects that come with a robust DSL it's rather simple to restore the behavior of `constructor_type`. See https://github.com/dry-rb/dry-struct/pull/64 for details (flash-gordon)
|
57
|
+
|
58
|
+
Example: evaluate defaults on `nil` values
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class User < Dry::Struct
|
62
|
+
transform_types do |type|
|
63
|
+
type.constructor { |value| value.nil? ? Undefined : value }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
* `Data::Struct.transform_keys` accepts a block/proc that transforms keys of input hashes. The most obvious usage is simbolization but arbitrary transformations are allowed (flash-gordon)
|
69
|
+
|
70
|
+
* `Dry.Struct` builds a struct by a hash of attribute names and types (citizen428)
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
User = Dry::Struct(name: 'strict.string') do
|
74
|
+
attribute :email, 'strict.string'
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
* Support for `Struct.meta`, note that `.meta` returns a _new class_ (flash-gordon)
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class User < Dry::Struct
|
82
|
+
attribute :name, Dry::Types['strict.string']
|
83
|
+
end
|
84
|
+
|
85
|
+
UserWithMeta = User.meta(foo: :bar)
|
86
|
+
|
87
|
+
User.new(name: 'Jade').class == UserWithMeta.new(name: 'Jade').class # => false
|
88
|
+
```
|
89
|
+
|
90
|
+
* `Struct.attribute` yields a block with definition for nested structs. It defines a nested constant for the new struct and supports arrays (AMHOL + flash-gordon)
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class User < Dry::Struct
|
94
|
+
attribute :name, Types::Strict::String
|
95
|
+
attribute :address do
|
96
|
+
attribute :country, Types::Strict::String
|
97
|
+
attribute :city, Types::Strict::String
|
98
|
+
end
|
99
|
+
attribute :accounts, Types::Strict::Array do
|
100
|
+
attribute :currency, Types::Strict::String
|
101
|
+
attribute :balance, Types::Strict::Decimal
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# ^This automatically defines User::Address and User::Account
|
106
|
+
```
|
107
|
+
|
108
|
+
## Fixed
|
109
|
+
|
110
|
+
* Adding a new attribute invalidates `attribute_names` (flash-gordon)
|
111
|
+
* Struct classes track subclasses and define attributes in them, now it doesn't matter whether you define attributes first and _then_ subclass or vice versa. Note this can lead to memory leaks in Rails environment when struct classes are reloaded (flash-gordon)
|
112
|
+
|
113
|
+
[Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-struct/compare/v0.4.0...v0.5.0)
|
114
|
+
|
1
115
|
# v0.4.0 2017-11-04
|
2
116
|
|
3
117
|
## Changed
|
data/CONTRIBUTING.md
CHANGED
@@ -6,11 +6,11 @@ If you found a bug, report an issue and describe what's the expected behavior ve
|
|
6
6
|
|
7
7
|
## Reporting feature requests
|
8
8
|
|
9
|
-
Report a feature request **only after
|
9
|
+
Report a feature request **only after discourseing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discourseion thread, and instead summarize what was discourseed.
|
10
10
|
|
11
11
|
## Reporting questions, support requests, ideas, concerns etc.
|
12
12
|
|
13
|
-
**PLEASE DON'T** - use [
|
13
|
+
**PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
|
14
14
|
|
15
15
|
# Pull Request Guidelines
|
16
16
|
|
@@ -26,4 +26,4 @@ Other requirements:
|
|
26
26
|
|
27
27
|
# Asking for help
|
28
28
|
|
29
|
-
If these guidelines aren't helpful, and you're stuck, please post a message on [
|
29
|
+
If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org).
|
data/Gemfile
CHANGED
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem 'dry-types', git: 'https://github.com/dry-rb/dry-types'
|
6
|
+
gem 'dry-inflector', git: 'https://github.com/dry-rb/dry-inflector'
|
6
7
|
|
7
8
|
group :test do
|
8
9
|
gem 'codeclimate-test-reporter', platform: :mri, require: false
|
@@ -11,7 +12,8 @@ group :test do
|
|
11
12
|
end
|
12
13
|
|
13
14
|
group :tools do
|
14
|
-
gem 'byebug', platform: :mri
|
15
|
+
gem 'pry-byebug', platform: :mri
|
16
|
+
gem 'pry', platform: :jruby
|
15
17
|
gem 'mutant'
|
16
18
|
gem 'mutant-rspec'
|
17
19
|
end
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/dry-struct
|
2
2
|
[travis]: https://travis-ci.org/dry-rb/dry-struct
|
3
|
-
[gemnasium]: https://gemnasium.com/dry-rb/dry-struct
|
4
3
|
[codeclimate]: https://codeclimate.com/github/dry-rb/dry-struct
|
5
4
|
[coveralls]: https://coveralls.io/r/dry-rb/dry-struct
|
6
5
|
[inchpages]: http://inch-ci.org/github/dry-rb/dry-struct
|
@@ -9,7 +8,6 @@
|
|
9
8
|
|
10
9
|
[][gem]
|
11
10
|
[][travis]
|
12
|
-
[][gemnasium]
|
13
11
|
[][codeclimate]
|
14
12
|
[][codeclimate]
|
15
13
|
[][inchpages]
|
data/bin/console
CHANGED
@@ -3,12 +3,10 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'dry/struct'
|
5
5
|
|
6
|
-
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
6
|
+
require 'irb'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
module Types
|
9
|
+
include Dry::Types.module
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
IRB.start
|
12
|
+
binding.irb
|
data/dry-struct.gemspec
CHANGED
@@ -17,6 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
# delete this section to allow pushing this gem to any host.
|
18
18
|
if spec.respond_to?(:metadata)
|
19
19
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-struct/blob/master/CHANGELOG.md'
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-struct'
|
20
22
|
else
|
21
23
|
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
22
24
|
end
|
@@ -27,8 +29,8 @@ Gem::Specification.new do |spec|
|
|
27
29
|
spec.require_paths = ['lib']
|
28
30
|
|
29
31
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
30
|
-
spec.add_runtime_dependency 'dry-types', '~> 0.
|
31
|
-
spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.
|
32
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.13'
|
33
|
+
spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.3'
|
32
34
|
spec.add_runtime_dependency 'ice_nine', '~> 0.11'
|
33
35
|
|
34
36
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'dry/core/class_attributes'
|
2
|
-
require 'dry/
|
2
|
+
require 'dry/core/inflector'
|
3
|
+
require 'dry/core/descendants_tracker'
|
3
4
|
|
4
5
|
require 'dry/struct/errors'
|
5
6
|
require 'dry/struct/constructor'
|
7
|
+
require 'dry/struct/sum'
|
6
8
|
|
7
9
|
module Dry
|
8
10
|
class Struct
|
@@ -17,31 +19,118 @@ module Dry
|
|
17
19
|
def inherited(klass)
|
18
20
|
super
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
base = self
|
23
|
+
|
24
|
+
klass.class_eval do
|
25
|
+
@meta = base.meta
|
26
|
+
|
27
|
+
unless equal?(Value)
|
28
|
+
extend Dry::Core::DescendantsTracker
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
23
32
|
|
24
33
|
# Adds an attribute for this {Struct} with given `name` and `type`
|
25
34
|
# and modifies {.schema} accordingly.
|
26
35
|
#
|
27
36
|
# @param [Symbol] name name of the defined attribute
|
28
|
-
# @param [Dry::Types::Definition] type
|
37
|
+
# @param [Dry::Types::Definition, nil] type or superclass of nested type
|
29
38
|
# @return [Dry::Struct]
|
39
|
+
# @yield
|
40
|
+
# If a block is given, it will be evaluated in the context of
|
41
|
+
# a new struct class, and set as a nested type for the given
|
42
|
+
# attribute. A class with a matching name will also be defined for
|
43
|
+
# the nested type.
|
30
44
|
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
31
45
|
# same name as previously defined one
|
32
46
|
#
|
33
|
-
# @example
|
47
|
+
# @example with nested structs
|
48
|
+
# class Language < Dry::Struct
|
49
|
+
# attribute :name, Types::String
|
50
|
+
# attribute :details, Dry::Struct do
|
51
|
+
# attribute :type, Types::String
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# Language.schema
|
56
|
+
# #=> {
|
57
|
+
# :name=>#<Dry::Types::Definition primitive=String options={} meta={}>,
|
58
|
+
# :details=>Language::Details
|
59
|
+
# }
|
60
|
+
#
|
61
|
+
# ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
|
62
|
+
# ruby.name #=> 'Ruby'
|
63
|
+
# ruby.details #=> #<Language::Details type="OO">
|
64
|
+
# ruby.details.type #=> 'OO'
|
65
|
+
#
|
66
|
+
# @example with a nested array of structs
|
34
67
|
# class Language < Dry::Struct
|
35
68
|
# attribute :name, Types::String
|
69
|
+
# array :versions, Types::String
|
70
|
+
# array :celebrities, Types::Array.of(Dry::Struct) do
|
71
|
+
# attribute :name, Types::String
|
72
|
+
# attribute :pseudonym, Types::String
|
73
|
+
# end
|
36
74
|
# end
|
37
75
|
#
|
38
76
|
# Language.schema
|
39
|
-
# #=> {
|
77
|
+
# #=> {
|
78
|
+
# :name=>#<Dry::Types::Definition primitive=String options={} meta={}>,
|
79
|
+
# :versions=>#<Dry::Types::Array::Member primitive=Array options={:member=>#<Dry::Types::Definition primitive=String options={} meta={}>} meta={}>,
|
80
|
+
# :celebrities=>#<Dry::Types::Array::Member primitive=Array options={:member=>Language::Celebrity} meta={}>
|
81
|
+
# }
|
40
82
|
#
|
41
|
-
# ruby = Language.new(
|
83
|
+
# ruby = Language.new(
|
84
|
+
# name: 'Ruby',
|
85
|
+
# versions: %w(1.8.7 1.9.8 2.0.1),
|
86
|
+
# celebrities: [
|
87
|
+
# { name: 'Yukihiro Matsumoto', pseudonym: 'Matz' },
|
88
|
+
# { name: 'Aaron Patterson', pseudonym: 'tenderlove' }
|
89
|
+
# ]
|
90
|
+
# )
|
42
91
|
# ruby.name #=> 'Ruby'
|
43
|
-
|
44
|
-
|
92
|
+
# ruby.versions #=> ['1.8.7', '1.9.8', '2.0.1']
|
93
|
+
# ruby.celebrities
|
94
|
+
# #=> [
|
95
|
+
# #<Language::Celebrity name='Yukihiro Matsumoto' pseudonym='Matz'>,
|
96
|
+
# #<Language::Celebrity name='Aaron Patterson' pseudonym='tenderlove'>
|
97
|
+
# ]
|
98
|
+
# ruby.celebrities[0].name #=> 'Yukihiro Matsumoto'
|
99
|
+
# ruby.celebrities[0].pseudonym #=> 'Matz'
|
100
|
+
# ruby.celebrities[1].name #=> 'Aaron Patterson'
|
101
|
+
# ruby.celebrities[1].pseudonym #=> 'tenderlove'
|
102
|
+
def attribute(name, type = nil, &block)
|
103
|
+
attributes(name => build_type(name, type, &block))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds an omittable (key is not required on initialization) attribute for this {Struct}
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# class User < Dry::Struct
|
110
|
+
# attribute :name, Types::Strict::String
|
111
|
+
# attribute? :email, Types::Strict::String
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# User.new(name: 'John') # => #<User name="John">
|
115
|
+
#
|
116
|
+
# @param [Symbol] name name of the defined attribute
|
117
|
+
# @param [Dry::Types::Definition, nil] type or superclass of nested type
|
118
|
+
# @return [Dry::Struct]
|
119
|
+
#
|
120
|
+
def attribute?(*args, &block)
|
121
|
+
if args.size == 1 && block.nil?
|
122
|
+
Dry::Core::Deprecations.warn(
|
123
|
+
'Dry::Struct.attribute? is deprecated for checking attribute presence, '\
|
124
|
+
'use has_attribute? instead',
|
125
|
+
tag: :'dry-struct'
|
126
|
+
)
|
127
|
+
|
128
|
+
has_attribute?(args[0])
|
129
|
+
else
|
130
|
+
name, type = args
|
131
|
+
|
132
|
+
attribute(name, build_type(name, type, &block).meta(omittable: true))
|
133
|
+
end
|
45
134
|
end
|
46
135
|
|
47
136
|
# @param [Hash{Symbol => Dry::Types::Definition}] new_schema
|
@@ -50,7 +139,7 @@ module Dry
|
|
50
139
|
# same name as previously defined one
|
51
140
|
# @see #attribute
|
52
141
|
# @example
|
53
|
-
# class
|
142
|
+
# class Book < Dry::Struct
|
54
143
|
# attributes(
|
55
144
|
# title: Types::String,
|
56
145
|
# author: Types::String
|
@@ -63,18 +152,60 @@ module Dry
|
|
63
152
|
def attributes(new_schema)
|
64
153
|
check_schema_duplication(new_schema)
|
65
154
|
|
66
|
-
|
67
|
-
input Types['coercible.hash'].public_send(constructor_type, schema)
|
155
|
+
input input.schema(new_schema)
|
68
156
|
|
69
157
|
new_schema.each_key do |key|
|
70
|
-
|
158
|
+
next if instance_methods.include?(key)
|
159
|
+
class_eval(<<-RUBY)
|
160
|
+
def #{ key }
|
161
|
+
@attributes[#{ key.inspect }]
|
162
|
+
end
|
163
|
+
RUBY
|
71
164
|
end
|
72
165
|
|
73
|
-
|
166
|
+
@attribute_names = nil
|
167
|
+
|
168
|
+
descendants.
|
169
|
+
select { |d| d.superclass == self }.
|
170
|
+
each { |d| d.attributes(new_schema.reject { |k, _| d.schema.key?(k) }) }
|
74
171
|
|
75
172
|
self
|
76
173
|
end
|
77
174
|
|
175
|
+
# Add an arbitrary transformation for new attribute types.
|
176
|
+
#
|
177
|
+
# @param [#call,nil] proc
|
178
|
+
# @param [#call,nil] block
|
179
|
+
# @example
|
180
|
+
# class Book < Dry::Struct
|
181
|
+
# transform_types { |t| t.meta(struct: :Book) }
|
182
|
+
#
|
183
|
+
# attribute :title, Types::Strict::String
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# Book.schema[:title].meta # => { struct: :Book }
|
187
|
+
#
|
188
|
+
def transform_types(proc = nil, &block)
|
189
|
+
input input.with_type_transform(proc || block)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Add an arbitrary transformation for input hash keys.
|
193
|
+
#
|
194
|
+
# @param [#call,nil] proc
|
195
|
+
# @param [#call,nil] block
|
196
|
+
# @example
|
197
|
+
# class Book < Dry::Struct
|
198
|
+
# transform_keys(&:to_sym)
|
199
|
+
#
|
200
|
+
# attribute :title, Types::Strict::String
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# Book.new('title' => "The Old Man and the Sea")
|
204
|
+
# # => #<Book title="The Old Man and the Sea">
|
205
|
+
def transform_keys(proc = nil, &block)
|
206
|
+
input input.with_key_transform(proc || block)
|
207
|
+
end
|
208
|
+
|
78
209
|
# @param [Hash{Symbol => Dry::Types::Definition, Dry::Struct}] new_schema
|
79
210
|
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
80
211
|
# same name as previously defined one
|
@@ -87,7 +218,6 @@ module Dry
|
|
87
218
|
|
88
219
|
# @param [Hash{Symbol => Object},Dry::Struct] attributes
|
89
220
|
# @raise [Struct::Error] if the given attributes don't conform {#schema}
|
90
|
-
# with given {#constructor_type}
|
91
221
|
def new(attributes = default_attributes)
|
92
222
|
if attributes.instance_of?(self)
|
93
223
|
attributes
|
@@ -110,34 +240,14 @@ module Dry
|
|
110
240
|
alias_method :[], :call
|
111
241
|
|
112
242
|
# @param [#call,nil] constructor
|
113
|
-
# @param [Hash]
|
243
|
+
# @param [Hash] _options
|
114
244
|
# @param [#call,nil] block
|
115
245
|
# @return [Dry::Struct::Constructor]
|
116
246
|
def constructor(constructor = nil, **_options, &block)
|
117
247
|
Struct::Constructor.new(self, fn: constructor || block)
|
118
248
|
end
|
119
249
|
|
120
|
-
#
|
121
|
-
# Used in a {Struct} constructor if no attributes provided to {.new}
|
122
|
-
#
|
123
|
-
# @return [Hash{Symbol => Object}]
|
124
|
-
def default_attributes
|
125
|
-
check_invalid_schema_keys
|
126
|
-
schema.each_with_object({}) { |(name, type), result|
|
127
|
-
result[name] = type.evaluate if type.default?
|
128
|
-
}
|
129
|
-
end
|
130
|
-
|
131
|
-
def check_invalid_schema_keys
|
132
|
-
invalid_keys = schema.select { |name, type| type.instance_of?(String) }
|
133
|
-
raise ArgumentError, argument_error_msg(invalid_keys.keys) if invalid_keys.any?
|
134
|
-
end
|
135
|
-
|
136
|
-
def argument_error_msg(keys)
|
137
|
-
"Invaild argument for #{keys.join(', ')}"
|
138
|
-
end
|
139
|
-
|
140
|
-
# @param [Hash{Symbol => Object}] input
|
250
|
+
# @param [Hash{Symbol => Object},Dry::Struct] input
|
141
251
|
# @yieldparam [Dry::Types::Result::Failure] failure
|
142
252
|
# @yieldreturn [Dry::Types::ResultResult]
|
143
253
|
# @return [Dry::Types::Result]
|
@@ -148,6 +258,17 @@ module Dry
|
|
148
258
|
block_given? ? yield(failure) : failure
|
149
259
|
end
|
150
260
|
|
261
|
+
# @param [Hash{Symbol => Object},Dry::Struct] input
|
262
|
+
# @return [Dry::Types::Result]
|
263
|
+
# @private
|
264
|
+
def try_struct(input)
|
265
|
+
if input.is_a?(self)
|
266
|
+
Types::Result::Success.new(input)
|
267
|
+
else
|
268
|
+
yield
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
151
272
|
# @param [({Symbol => Object})] args
|
152
273
|
# @return [Dry::Types::Result::Success]
|
153
274
|
def success(*args)
|
@@ -157,7 +278,7 @@ module Dry
|
|
157
278
|
# @param [({Symbol => Object})] args
|
158
279
|
# @return [Dry::Types::Result::Failure]
|
159
280
|
def failure(*args)
|
160
|
-
result(Types::Result::Failure, *args)
|
281
|
+
result(::Dry::Types::Result::Failure, *args)
|
161
282
|
end
|
162
283
|
|
163
284
|
# @param [Class] klass
|
@@ -196,16 +317,94 @@ module Dry
|
|
196
317
|
#
|
197
318
|
# @param [Symbol] key Attribute name
|
198
319
|
# @return [Boolean]
|
199
|
-
def
|
320
|
+
def has_attribute?(key)
|
200
321
|
schema.key?(key)
|
201
322
|
end
|
202
323
|
|
324
|
+
# @return [Hash{Symbol => Dry::Types::Definition, Dry::Struct}]
|
325
|
+
def schema
|
326
|
+
input.member_types
|
327
|
+
end
|
328
|
+
|
203
329
|
# Gets the list of attribute names
|
204
330
|
#
|
205
331
|
# @return [Array<Symbol>]
|
206
332
|
def attribute_names
|
207
333
|
@attribute_names ||= schema.keys
|
208
334
|
end
|
335
|
+
|
336
|
+
# @return [{Symbol => Object}]
|
337
|
+
def meta(meta = Undefined)
|
338
|
+
if meta.equal?(Undefined)
|
339
|
+
@meta
|
340
|
+
else
|
341
|
+
Class.new(self) do
|
342
|
+
@meta = @meta.merge(meta) unless meta.empty?
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Build a sum type
|
348
|
+
# @param [Dry::Types::Type] type
|
349
|
+
# @return [Dry::Types::Sum]
|
350
|
+
def |(type)
|
351
|
+
if type.is_a?(Class) && type <= Struct
|
352
|
+
Struct::Sum.new(self, type)
|
353
|
+
else
|
354
|
+
super
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Stores an object for building nested struct classes
|
359
|
+
# @return [StructBuilder]
|
360
|
+
def struct_builder
|
361
|
+
@struct_builder ||= StructBuilder.new(self).freeze
|
362
|
+
end
|
363
|
+
private :struct_builder
|
364
|
+
|
365
|
+
# Retrieves default attributes from defined {.schema}.
|
366
|
+
# Used in a {Struct} constructor if no attributes provided to {.new}
|
367
|
+
#
|
368
|
+
# @return [Hash{Symbol => Object}]
|
369
|
+
def default_attributes(default_schema = schema)
|
370
|
+
default_schema.each_with_object({}) do |(name, type), result|
|
371
|
+
result[name] = default_attributes(type.schema) if struct?(type)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
private :default_attributes
|
375
|
+
|
376
|
+
# Checks if the given type is a Dry::Struct
|
377
|
+
#
|
378
|
+
# @param [Dry::Types::Definition, Dry::Struct] type
|
379
|
+
# @return [Boolean]
|
380
|
+
def struct?(type)
|
381
|
+
type.is_a?(Class) && type <= Struct
|
382
|
+
end
|
383
|
+
private :struct?
|
384
|
+
|
385
|
+
# Constructs a type
|
386
|
+
#
|
387
|
+
# @return [Dry::Types::Type, Dry::Struct]
|
388
|
+
def build_type(name, type, &block)
|
389
|
+
type_object =
|
390
|
+
if type.is_a?(String)
|
391
|
+
Dry::Types[type]
|
392
|
+
elsif block.nil? && type.nil?
|
393
|
+
raise(
|
394
|
+
ArgumentError,
|
395
|
+
'you must supply a type or a block to `Dry::Struct.attribute`'
|
396
|
+
)
|
397
|
+
else
|
398
|
+
type
|
399
|
+
end
|
400
|
+
|
401
|
+
if block
|
402
|
+
struct_builder.(name, type_object, &block)
|
403
|
+
else
|
404
|
+
type_object
|
405
|
+
end
|
406
|
+
end
|
407
|
+
private :build_type
|
209
408
|
end
|
210
409
|
end
|
211
410
|
end
|
data/lib/dry/struct/errors.rb
CHANGED
@@ -11,5 +11,12 @@ module Dry
|
|
11
11
|
super("Attribute :#{key} has already been defined")
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
# Raised when a struct doesn't have an attribute
|
16
|
+
class MissingAttributeError < KeyError
|
17
|
+
def initialize(key)
|
18
|
+
super("Missing attribute: #{ key.inspect }")
|
19
|
+
end
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Struct
|
5
|
+
def pretty_print(pp)
|
6
|
+
klass = self.class
|
7
|
+
pp.group(1, "#<#{ klass.name || klass.inspect }", '>') do
|
8
|
+
pp.seplist(@attributes.keys, proc { pp.text ',' }) do |column_name|
|
9
|
+
column_value = @attributes[column_name]
|
10
|
+
pp.breakable ' '
|
11
|
+
pp.group(1) do
|
12
|
+
pp.text column_name
|
13
|
+
pp.text '='
|
14
|
+
pp.pp column_value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/dry/struct/hashify.rb
CHANGED
@@ -7,9 +7,13 @@ module Dry
|
|
7
7
|
# @return [Hash, Array]
|
8
8
|
def self.[](value)
|
9
9
|
if value.respond_to?(:to_hash)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
if RUBY_VERSION >= '2.4'
|
11
|
+
value.to_hash.transform_values { |v| self[v] }
|
12
|
+
else
|
13
|
+
value.to_hash.each_with_object({}) { |(k, v), h| h[k] = self[v] }
|
14
|
+
end
|
15
|
+
elsif value.respond_to?(:to_ary)
|
16
|
+
value.to_ary.map { |item| self[item] }
|
13
17
|
else
|
14
18
|
value
|
15
19
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'dry/types/compiler'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Struct
|
5
|
+
# @private
|
6
|
+
class StructBuilder < Dry::Types::Compiler
|
7
|
+
attr_reader :struct
|
8
|
+
|
9
|
+
def initialize(struct)
|
10
|
+
super(Dry::Types)
|
11
|
+
@struct = struct
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Symbol|String] attr_name the name of the nested type
|
15
|
+
# @param [Dry::Struct,Dry::Types::Type::Array] type the superclass of the nested struct
|
16
|
+
# @yield the body of the nested struct
|
17
|
+
def call(attr_name, type, &block)
|
18
|
+
const_name = const_name(type, attr_name)
|
19
|
+
check_name(const_name)
|
20
|
+
|
21
|
+
new_type = Class.new(parent(type), &block)
|
22
|
+
struct.const_set(const_name, new_type)
|
23
|
+
|
24
|
+
if array?(type)
|
25
|
+
type.of(new_type)
|
26
|
+
else
|
27
|
+
new_type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def array?(type)
|
34
|
+
type.is_a?(Types::Type) && type.primitive.equal?(Array)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent(type)
|
38
|
+
if array?(type)
|
39
|
+
visit(type.to_ast)
|
40
|
+
else
|
41
|
+
type || default_superclass
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_superclass
|
46
|
+
struct < Value ? Value : Struct
|
47
|
+
end
|
48
|
+
|
49
|
+
def const_name(type, attr_name)
|
50
|
+
snake_name = if array?(type)
|
51
|
+
Dry::Core::Inflector.singularize(attr_name)
|
52
|
+
else
|
53
|
+
attr_name
|
54
|
+
end
|
55
|
+
|
56
|
+
Dry::Core::Inflector.camelize(snake_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_name(name)
|
60
|
+
raise(
|
61
|
+
Struct::Error,
|
62
|
+
"Can't create nested attribute - `#{struct}::#{name}` already defined"
|
63
|
+
) if struct.const_defined?(name, false)
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_constrained(node)
|
67
|
+
definition, * = node
|
68
|
+
visit(definition)
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit_array(node)
|
72
|
+
member, * = node
|
73
|
+
member
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_definition(*)
|
77
|
+
default_superclass
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_constructor(node)
|
81
|
+
definition, * = node
|
82
|
+
visit(definition)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'dry/types/sum'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class Struct
|
5
|
+
# A sum type of two or more structs
|
6
|
+
# As opposed to Dry::Types::Sum::Constrained
|
7
|
+
# this type tries no to coerce data first.
|
8
|
+
class Sum < Dry::Types::Sum::Constrained
|
9
|
+
# @param [Hash{Symbol => Object},Dry::Struct] input
|
10
|
+
# @yieldparam [Dry::Types::Result::Failure] failure
|
11
|
+
# @yieldreturn [Dry::Types::ResultResult]
|
12
|
+
# @return [Dry::Types::Result]
|
13
|
+
def try(input)
|
14
|
+
if input.is_a?(Struct)
|
15
|
+
try_struct(input) { super }
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build a new sum type
|
22
|
+
# @param [Dry::Types::Type] type
|
23
|
+
# @return [Dry::Types::Sum]
|
24
|
+
def |(type)
|
25
|
+
if type.is_a?(Class) && type <= Struct || type.is_a?(Sum)
|
26
|
+
self.class.new(self, type)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def try_struct(input)
|
36
|
+
left.try_struct(input) do
|
37
|
+
right.try_struct(input) { yield }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/dry/struct/version.rb
CHANGED
data/lib/dry/struct.rb
CHANGED
@@ -1,12 +1,39 @@
|
|
1
|
-
require 'dry/core/constants'
|
2
1
|
require 'dry-types'
|
2
|
+
require 'dry-equalizer'
|
3
|
+
require 'dry/core/extensions'
|
4
|
+
require 'dry/core/constants'
|
3
5
|
|
4
6
|
require 'dry/struct/version'
|
5
7
|
require 'dry/struct/errors'
|
6
8
|
require 'dry/struct/class_interface'
|
7
9
|
require 'dry/struct/hashify'
|
10
|
+
require 'dry/struct/struct_builder'
|
8
11
|
|
9
12
|
module Dry
|
13
|
+
# Constructor method for easily creating a {Dry::Struct}.
|
14
|
+
# @return [Dry::Struct]
|
15
|
+
# @example
|
16
|
+
# require 'dry-struct'
|
17
|
+
#
|
18
|
+
# module Types
|
19
|
+
# include Dry::Types.module
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Person = Dry.Struct(name: Types::Strict::String, age: Types::Strict::Int)
|
23
|
+
# matz = Person.new(name: "Matz", age: 52)
|
24
|
+
# matz.name #=> "Matz"
|
25
|
+
# matz.age #=> 52
|
26
|
+
#
|
27
|
+
# Test = Dry.Struct(expected: Types::Strict::String) { input(input.strict) }
|
28
|
+
# Test[expected: "foo", unexpected: "bar"]
|
29
|
+
# #=> Dry::Struct::Error: [Test.new] unexpected keys [:unexpected] in Hash input
|
30
|
+
def self.Struct(attributes = Dry::Core::Constants::EMPTY_HASH, &block)
|
31
|
+
Class.new(Dry::Struct) do
|
32
|
+
attributes.each { |a, type| attribute a, type }
|
33
|
+
instance_eval(&block) if block
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
10
37
|
# Typed {Struct} with virtus-like DSL for defining schema.
|
11
38
|
#
|
12
39
|
# ### Differences between dry-struct and virtus
|
@@ -18,14 +45,12 @@ module Dry
|
|
18
45
|
# * Handling of attribute values is provided by standalone type objects from
|
19
46
|
# [`dry-types`][].
|
20
47
|
# * Handling of attribute hashes is provided by standalone hash schemas from
|
21
|
-
# [`dry-types`][]
|
22
|
-
# {Struct} (see {Dry::Struct::ClassInterface#constructor_type})
|
48
|
+
# [`dry-types`][].
|
23
49
|
# * Struct classes quack like [`dry-types`][], which means you can use them
|
24
50
|
# in hash schemas, as array members or sum them
|
25
51
|
#
|
26
52
|
# {Struct} class can specify a constructor type, which uses [hash schemas][]
|
27
53
|
# to handle attributes in `.new` method.
|
28
|
-
# See {ClassInterface#new} for constructor types descriptions and examples.
|
29
54
|
#
|
30
55
|
# [`dry-types`]: https://github.com/dry-rb/dry-types
|
31
56
|
# [Virtus]: https://github.com/solnic/virtus
|
@@ -57,130 +82,26 @@ module Dry
|
|
57
82
|
# refactoring.title #=> 'Refactoring'
|
58
83
|
# refactoring.subtitle #=> 'Improving the Design of Existing Code'
|
59
84
|
class Struct
|
85
|
+
extend Dry::Core::Extensions
|
60
86
|
include Dry::Core::Constants
|
61
87
|
extend ClassInterface
|
62
88
|
|
63
|
-
|
64
|
-
# @return [Dry::Types::Hash]
|
65
|
-
# @see #constructor_type
|
66
|
-
defines :input
|
67
|
-
input Types['coercible.hash']
|
68
|
-
|
69
|
-
# @return [Hash{Symbol => Dry::Types::Definition, Dry::Struct}]
|
70
|
-
defines :schema
|
71
|
-
schema EMPTY_HASH
|
89
|
+
include Dry::Equalizer(:__attributes__)
|
72
90
|
|
73
|
-
|
91
|
+
# {Dry::Types::Hash::Schema} subclass with specific behaviour defined for
|
92
|
+
# @return [Dry::Types::Hash::Schema]
|
93
|
+
defines :input
|
94
|
+
input Types['coercible.hash'].schema(EMPTY_HASH)
|
74
95
|
|
75
|
-
|
76
|
-
#
|
77
|
-
# @note All examples below assume that you have defined {Struct} with
|
78
|
-
# following attributes and explicitly call only {#constructor_type}:
|
79
|
-
#
|
80
|
-
# ```ruby
|
81
|
-
# class User < Dry::Struct
|
82
|
-
# attribute :name, Types::Strict::String.default('John Doe')
|
83
|
-
# attribute :age, Types::Strict::Int
|
84
|
-
# end
|
85
|
-
# ```
|
86
|
-
#
|
87
|
-
# ### Common constructor types include:
|
88
|
-
#
|
89
|
-
# * `:permissive` - the default constructor type, useful for defining
|
90
|
-
# {Struct}s that are instantiated using data from the database
|
91
|
-
# (i.e. results of a database query), where you expect *all defined
|
92
|
-
# attributes to be present* and it's OK to ignore other keys
|
93
|
-
# (i.e. keys used for joining, that are not relevant from your domain
|
94
|
-
# {Struct}s point of view). Default values **are not used** otherwise
|
95
|
-
# you wouldn't notice missing data.
|
96
|
-
# * `:schema` - missing keys will result in setting them using default
|
97
|
-
# values, unexpected keys will be ignored.
|
98
|
-
# * `:strict` - useful when you *do not expect keys other than the ones
|
99
|
-
# you specified as attributes* in the input hash
|
100
|
-
# * `:strict_with_defaults` - same as `:strict` but you are OK that some
|
101
|
-
# values may be nil and you want defaults to be set
|
102
|
-
#
|
103
|
-
# To feel the difference between constructor types, look into examples.
|
104
|
-
# Each of them provide the same attributes' definitions,
|
105
|
-
# different constructor type, and 4 cases of given input:
|
106
|
-
#
|
107
|
-
# 1. Input omits a key for a value that does not have a default
|
108
|
-
# 2. Input omits a key for a value that has a default
|
109
|
-
# 3. Input contains nil for a value that specifies a default
|
110
|
-
# 4. Input includes a key that was not specified in the schema
|
111
|
-
#
|
112
|
-
# @example `:permissive` constructor
|
113
|
-
# class User < Dry::Struct
|
114
|
-
# constructor_type :permissive
|
115
|
-
# end
|
116
|
-
#
|
117
|
-
# User.new(name: "Jane")
|
118
|
-
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
119
|
-
# User.new(age: 31)
|
120
|
-
# #=> Dry::Struct::Error: [User.new] :name is missing in Hash input
|
121
|
-
# User.new(name: nil, age: 31)
|
122
|
-
# #=> #<User name="John Doe" age=31>
|
123
|
-
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
124
|
-
# #=> #<User name="Jane" age=31>
|
125
|
-
#
|
126
|
-
# @example `:schema` constructor
|
127
|
-
# class User < Dry::Struct
|
128
|
-
# constructor_type :schema
|
129
|
-
# end
|
130
|
-
#
|
131
|
-
# User.new(name: "Jane") #=> #<User name="Jane" age=nil>
|
132
|
-
# User.new(age: 31) #=> #<User name="John Doe" age=31>
|
133
|
-
# User.new(name: nil, age: 31) #=> #<User name="John Doe" age=31>
|
134
|
-
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
135
|
-
# #=> #<User name="Jane" age=31>
|
136
|
-
#
|
137
|
-
# @example `:strict` constructor
|
138
|
-
# class User < Dry::Struct
|
139
|
-
# constructor_type :strict
|
140
|
-
# end
|
141
|
-
#
|
142
|
-
# User.new(name: "Jane")
|
143
|
-
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
144
|
-
# User.new(age: 31)
|
145
|
-
# #=> Dry::Struct::Error: [User.new] :name is missing in Hash input
|
146
|
-
# User.new(name: nil, age: 31)
|
147
|
-
# #=> Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
|
148
|
-
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
149
|
-
# #=> Dry::Struct::Error: [User.new] unexpected keys [:unexpected] in Hash input
|
150
|
-
#
|
151
|
-
# @example `:strict_with_defaults` constructor
|
152
|
-
# class User < Dry::Struct
|
153
|
-
# constructor_type :strict_with_defaults
|
154
|
-
# end
|
155
|
-
#
|
156
|
-
# User.new(name: "Jane")
|
157
|
-
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
158
|
-
# User.new(age: 31)
|
159
|
-
# #=> #<User name="John Doe" age=31>
|
160
|
-
# User.new(name: nil, age: 31)
|
161
|
-
# #=> Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
|
162
|
-
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
163
|
-
# #=> Dry::Struct::Error: [User.new] unexpected keys [:unexpected] in Hash input
|
164
|
-
#
|
165
|
-
# @see http://dry-rb.org/gems/dry-types/hash-schemas
|
166
|
-
#
|
167
|
-
# @overload constructor_type(type)
|
168
|
-
# Sets the constructor type for {Struct}
|
169
|
-
# @param [Symbol] type one of constructor types, see above
|
170
|
-
# @return [Symbol]
|
171
|
-
#
|
172
|
-
# @overload constructor_type
|
173
|
-
# Returns the constructor type for {Struct}
|
174
|
-
# @return [Symbol] (:strict)
|
175
|
-
defines :constructor_type, type: CONSTRUCTOR_TYPE
|
176
|
-
constructor_type :permissive
|
96
|
+
@meta = EMPTY_HASH
|
177
97
|
|
178
|
-
#
|
179
|
-
|
98
|
+
# @!attribute [Hash{Symbol => Object}] attributes
|
99
|
+
attr_reader :attributes
|
100
|
+
alias_method :__attributes__, :attributes
|
180
101
|
|
181
102
|
# @param [Hash, #each] attributes
|
182
103
|
def initialize(attributes)
|
183
|
-
attributes
|
104
|
+
@attributes = attributes
|
184
105
|
end
|
185
106
|
|
186
107
|
# Retrieves value of previously defined attribute by its' `name`
|
@@ -201,7 +122,7 @@ module Dry
|
|
201
122
|
# rom_n_roda[:title] #=> 'Web Development with ROM and Roda'
|
202
123
|
# rom_n_roda[:subtitle] #=> nil
|
203
124
|
def [](name)
|
204
|
-
|
125
|
+
@attributes.fetch(name) { raise MissingAttributeError.new(name) }
|
205
126
|
end
|
206
127
|
|
207
128
|
# Converts the {Dry::Struct} to a hash with keys representing
|
@@ -223,7 +144,7 @@ module Dry
|
|
223
144
|
# #=> {title: 'Web Development with ROM and Roda', subtitle: nil}
|
224
145
|
def to_hash
|
225
146
|
self.class.schema.keys.each_with_object({}) do |key, result|
|
226
|
-
result[key] = Hashify[self[key]]
|
147
|
+
result[key] = Hashify[self[key]] if attributes.key?(key)
|
227
148
|
end
|
228
149
|
end
|
229
150
|
alias_method :to_h, :to_hash
|
@@ -246,21 +167,21 @@ module Dry
|
|
246
167
|
# )
|
247
168
|
# #=> #<Book title="Web Development with ROM and Roda" subtitle="2nd edition">
|
248
169
|
#
|
249
|
-
# rom_n_roda.new(subtitle: '
|
250
|
-
# #=> #<Book title="Web Development with ROM and Roda" subtitle="
|
170
|
+
# rom_n_roda.new(subtitle: '3rd edition')
|
171
|
+
# #=> #<Book title="Web Development with ROM and Roda" subtitle="3rd edition">
|
251
172
|
def new(changeset)
|
252
173
|
self.class[__attributes__.merge(changeset)]
|
253
174
|
end
|
254
175
|
alias_method :__new__, :new
|
255
176
|
|
256
|
-
# @return[
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
end
|
177
|
+
# @return [String]
|
178
|
+
def inspect
|
179
|
+
klass = self.class
|
180
|
+
attrs = klass.attribute_names.map { |key| " #{key}=#{@attributes[key].inspect}" }.join
|
181
|
+
"#<#{ klass.name || klass.inspect }#{ attrs }>"
|
262
182
|
end
|
263
183
|
end
|
264
184
|
end
|
265
185
|
|
266
186
|
require 'dry/struct/value'
|
187
|
+
require 'dry/struct/extensions'
|
data/log/.gitkeep
ADDED
File without changes
|
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: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|
@@ -30,20 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
34
|
-
- - ">="
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: 0.12.2
|
33
|
+
version: '0.13'
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
38
|
- - "~>"
|
42
39
|
- !ruby/object:Gem::Version
|
43
|
-
version: '0.
|
44
|
-
- - ">="
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: 0.12.2
|
40
|
+
version: '0.13'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: dry-core
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +47,7 @@ dependencies:
|
|
53
47
|
version: '0.4'
|
54
48
|
- - ">="
|
55
49
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.4.
|
50
|
+
version: 0.4.3
|
57
51
|
type: :runtime
|
58
52
|
prerelease: false
|
59
53
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -63,7 +57,7 @@ dependencies:
|
|
63
57
|
version: '0.4'
|
64
58
|
- - ">="
|
65
59
|
- !ruby/object:Gem::Version
|
66
|
-
version: 0.4.
|
60
|
+
version: 0.4.3
|
67
61
|
- !ruby/object:Gem::Dependency
|
68
62
|
name: ice_nine
|
69
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,14 +155,21 @@ files:
|
|
161
155
|
- lib/dry/struct/class_interface.rb
|
162
156
|
- lib/dry/struct/constructor.rb
|
163
157
|
- lib/dry/struct/errors.rb
|
158
|
+
- lib/dry/struct/extensions.rb
|
159
|
+
- lib/dry/struct/extensions/pretty_print.rb
|
164
160
|
- lib/dry/struct/hashify.rb
|
161
|
+
- lib/dry/struct/struct_builder.rb
|
162
|
+
- lib/dry/struct/sum.rb
|
165
163
|
- lib/dry/struct/value.rb
|
166
164
|
- lib/dry/struct/version.rb
|
165
|
+
- log/.gitkeep
|
167
166
|
homepage: https://github.com/dry-rb/dry-struct
|
168
167
|
licenses:
|
169
168
|
- MIT
|
170
169
|
metadata:
|
171
170
|
allowed_push_host: https://rubygems.org
|
171
|
+
changelog_uri: https://github.com/dry-rb/dry-struct/blob/master/CHANGELOG.md
|
172
|
+
source_code_uri: https://github.com/dry-rb/dry-struct
|
172
173
|
post_install_message:
|
173
174
|
rdoc_options: []
|
174
175
|
require_paths:
|
@@ -185,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
186
|
version: '0'
|
186
187
|
requirements: []
|
187
188
|
rubyforge_project:
|
188
|
-
rubygems_version: 2.6
|
189
|
+
rubygems_version: 2.7.6
|
189
190
|
signing_key:
|
190
191
|
specification_version: 4
|
191
192
|
summary: Typed structs and value objects.
|