dry-struct 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +9 -4
- data/.yardopts +4 -0
- data/CHANGELOG.md +19 -0
- data/Rakefile +4 -0
- data/dry-struct.gemspec +2 -2
- data/lib/dry/struct.rb +206 -1
- data/lib/dry/struct/class_interface.rb +105 -33
- data/lib/dry/struct/errors.rb +4 -0
- data/lib/dry/struct/hashify.rb +4 -2
- data/lib/dry/struct/value.rb +20 -0
- data/lib/dry/struct/version.rb +1 -1
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cc9fef4b32f2b57bac889a7f5ca759795b650dd
|
4
|
+
data.tar.gz: be8ff9b280bcc8d0afdccf28d39453b7060e13d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8c76b9683ad51451060a3c388741598aedcac78946b0f979c8928fef1a3e6f4300c211438f3d3ca6490c23525730d051ffb7c2a808fd5e1b322474e91b20e9a
|
7
|
+
data.tar.gz: 0d8dc1bc47204f422ee104d60ee732e7bb276e3441ec255303f3a794f2718be0eb63bea2beda822c08a40a1a04a864d097171c4becaede57d7ffa701794a17fd
|
data/.travis.yml
CHANGED
@@ -5,16 +5,21 @@ bundler_args: --without benchmarks tools
|
|
5
5
|
script:
|
6
6
|
- bundle exec rake spec
|
7
7
|
after_success:
|
8
|
-
- '[
|
8
|
+
- '[ -d coverage ] && bundle exec codeclimate-test-reporter'
|
9
9
|
rvm:
|
10
10
|
- 2.1.10
|
11
|
-
- 2.2.
|
12
|
-
- 2.3.
|
11
|
+
- 2.2.7
|
12
|
+
- 2.3.3
|
13
|
+
- 2.4.1
|
13
14
|
- rbx-3
|
14
|
-
- jruby-9.1.
|
15
|
+
- jruby-9.1.8.0
|
15
16
|
env:
|
16
17
|
global:
|
18
|
+
- COVERAGE=true
|
17
19
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
20
|
+
matrix:
|
21
|
+
allow_failures:
|
22
|
+
- rvm: rbx-3
|
18
23
|
notifications:
|
19
24
|
email: false
|
20
25
|
webhooks:
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,28 @@
|
|
1
|
+
master
|
2
|
+
|
3
|
+
## Added
|
4
|
+
|
5
|
+
* `Dry::Struct#new` method to return new instance with applied changeset (Kukunin)
|
6
|
+
|
7
|
+
## Fixed
|
8
|
+
|
9
|
+
* `.[]` and `.call` does not coerce subclass to superclass anymore (Kukunin)
|
10
|
+
* Raise ArgumentError when attribute type is a string and no value provided is for `new` (GustavoCaso)
|
11
|
+
|
12
|
+
## Changed
|
13
|
+
|
14
|
+
* `.new` without arguments doesn't use nil as an input for non-default types anymore (flash-gordon)
|
15
|
+
|
16
|
+
[Compare v0.2.1...master](https://github.com/dry-rb/dry-struct/compare/v0.2.1...master)
|
17
|
+
|
1
18
|
# v0.2.1 2017-02-27
|
2
19
|
|
3
20
|
## Fixed
|
4
21
|
|
5
22
|
* Fixed `Dry::Struct::Value` which appeared to be broken in the last release (flash-gordon)
|
6
23
|
|
24
|
+
[Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-struct/compare/v0.2.0...v0.2.1)
|
25
|
+
|
7
26
|
# v0.2.0 2016-02-26
|
8
27
|
|
9
28
|
## Changed
|
data/Rakefile
CHANGED
data/dry-struct.gemspec
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
1
|
lib = File.expand_path('../lib', __FILE__)
|
4
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
3
|
require 'dry/struct/version'
|
@@ -31,9 +29,11 @@ Gem::Specification.new do |spec|
|
|
31
29
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
32
30
|
spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
|
33
31
|
spec.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.0'
|
32
|
+
spec.add_runtime_dependency 'dry-core', '~> 0.3'
|
34
33
|
spec.add_runtime_dependency 'ice_nine', '~> 0.11'
|
35
34
|
|
36
35
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
37
36
|
spec.add_development_dependency 'rake', '~> 11.0'
|
38
37
|
spec.add_development_dependency 'rspec', '~> 3.3'
|
38
|
+
spec.add_development_dependency 'yard', '~> 0.9.5'
|
39
39
|
end
|
data/lib/dry/struct.rb
CHANGED
@@ -6,25 +6,230 @@ require 'dry/struct/class_interface'
|
|
6
6
|
require 'dry/struct/hashify'
|
7
7
|
|
8
8
|
module Dry
|
9
|
+
# Typed {Struct} with virtus-like DSL for defining schema.
|
10
|
+
#
|
11
|
+
# ### Differences between dry-struct and virtus
|
12
|
+
#
|
13
|
+
# {Struct} look somewhat similar to [Virtus][] but there are few significant differences:
|
14
|
+
#
|
15
|
+
# * {Struct}s don't provide attribute writers and are meant to be used
|
16
|
+
# as "data objects" exclusively.
|
17
|
+
# * Handling of attribute values is provided by standalone type objects from
|
18
|
+
# [`dry-types`][].
|
19
|
+
# * Handling of attribute hashes is provided by standalone hash schemas from
|
20
|
+
# [`dry-types`][], which means there are different types of constructors in
|
21
|
+
# {Struct} (see {Dry::Struct::ClassInterface#constructor_type})
|
22
|
+
# * Struct classes quack like [`dry-types`][], which means you can use them
|
23
|
+
# in hash schemas, as array members or sum them
|
24
|
+
#
|
25
|
+
# {Struct} class can specify a constructor type, which uses [hash schemas][]
|
26
|
+
# to handle attributes in `.new` method.
|
27
|
+
# See {ClassInterface#new} for constructor types descriptions and examples.
|
28
|
+
#
|
29
|
+
# [`dry-types`]: https://github.com/dry-rb/dry-types
|
30
|
+
# [Virtus]: https://github.com/solnic/virtus
|
31
|
+
# [hash schemas]: http://dry-rb.org/gems/dry-types/hash-schemas
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# require 'dry-struct'
|
35
|
+
#
|
36
|
+
# module Types
|
37
|
+
# include Dry::Types.module
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# class Book < Dry::Struct
|
41
|
+
# attribute :title, Types::Strict::String
|
42
|
+
# attribute :subtitle, Types::Strict::String.optional
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# rom_n_roda = Book.new(
|
46
|
+
# title: 'Web Development with ROM and Roda',
|
47
|
+
# subtitle: nil
|
48
|
+
# )
|
49
|
+
# rom_n_roda.title #=> 'Web Development with ROM and Roda'
|
50
|
+
# rom_n_roda.subtitle #=> nil
|
51
|
+
#
|
52
|
+
# refactoring = Book.new(
|
53
|
+
# title: 'Refactoring',
|
54
|
+
# subtitle: 'Improving the Design of Existing Code'
|
55
|
+
# )
|
56
|
+
# refactoring.title #=> 'Refactoring'
|
57
|
+
# refactoring.subtitle #=> 'Improving the Design of Existing Code'
|
9
58
|
class Struct
|
10
59
|
extend ClassInterface
|
11
60
|
|
12
|
-
|
61
|
+
# {Dry::Types::Hash} subclass with specific behaviour defined for
|
62
|
+
# @return [Dry::Types::Hash]
|
63
|
+
# @see #constructor_type
|
64
|
+
defines :input
|
65
|
+
input Types['coercible.hash']
|
13
66
|
|
67
|
+
# Sets or retrieves {#constructor} type as a symbol
|
68
|
+
#
|
69
|
+
# @note All examples below assume that you have defined {Struct} with
|
70
|
+
# following attributes and explicitly call only {#constructor_type}:
|
71
|
+
#
|
72
|
+
# ```ruby
|
73
|
+
# class User < Dry::Struct
|
74
|
+
# attribute :name, Types::Strict::String.default('John Doe')
|
75
|
+
# attribute :age, Types::Strict::Int
|
76
|
+
# end
|
77
|
+
# ```
|
78
|
+
#
|
79
|
+
# ### Common constructor types include:
|
80
|
+
#
|
81
|
+
# * `:permissive` - the default constructor type, useful for defining
|
82
|
+
# {Struct}s that are instantiated using data from the database
|
83
|
+
# (i.e. results of a database query), where you expect *all defined
|
84
|
+
# attributes to be present* and it's OK to ignore other keys
|
85
|
+
# (i.e. keys used for joining, that are not relevant from your domain
|
86
|
+
# {Struct}s point of view). Default values **are not used** otherwise
|
87
|
+
# you wouldn't notice missing data.
|
88
|
+
# * `:schema` - missing keys will result in setting them using default
|
89
|
+
# values, unexpected keys will be ignored.
|
90
|
+
# * `:strict` - useful when you *do not expect keys other than the ones
|
91
|
+
# you specified as attributes* in the input hash
|
92
|
+
# * `:strict_with_defaults` - same as `:strict` but you are OK that some
|
93
|
+
# values may be nil and you want defaults to be set
|
94
|
+
#
|
95
|
+
# To feel the difference between constructor types, look into examples.
|
96
|
+
# Each of them provide the same attributes' definitions,
|
97
|
+
# different constructor type, and 4 cases of given input:
|
98
|
+
#
|
99
|
+
# 1. Input omits a key for a value that does not have a default
|
100
|
+
# 2. Input omits a key for a value that has a default
|
101
|
+
# 3. Input contains nil for a value that specifies a default
|
102
|
+
# 4. Input includes a key that was not specified in the schema
|
103
|
+
#
|
104
|
+
# @note Don’t use `:weak` and `:symbolized` as {#constructor_type},
|
105
|
+
# and instead use [`dry-validation`][] to process and validate
|
106
|
+
# attributes, otherwise your struct will behave as a data validator
|
107
|
+
# which raises exceptions on invalid input (assuming your attributes
|
108
|
+
# types are strict)
|
109
|
+
# [`dry-validation`]: https://github.com/dry-rb/dry-validation
|
110
|
+
#
|
111
|
+
# @example `:permissive` constructor
|
112
|
+
# class User < Dry::Struct
|
113
|
+
# constructor_type :permissive
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# User.new(name: "Jane")
|
117
|
+
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
118
|
+
# User.new(age: 31)
|
119
|
+
# #=> Dry::Struct::Error: [User.new] :name is missing in Hash input
|
120
|
+
# User.new(name: nil, age: 31)
|
121
|
+
# #=> #<User name="John Doe" age=31>
|
122
|
+
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
123
|
+
# #=> #<User name="Jane" age=31>
|
124
|
+
#
|
125
|
+
# @example `:schema` constructor
|
126
|
+
# class User < Dry::Struct
|
127
|
+
# constructor_type :schema
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# User.new(name: "Jane") #=> #<User name="Jane" age=nil>
|
131
|
+
# User.new(age: 31) #=> #<User name="John Doe" age=31>
|
132
|
+
# User.new(name: nil, age: 31) #=> #<User name="John Doe" age=31>
|
133
|
+
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
134
|
+
# #=> #<User name="Jane" age=31>
|
135
|
+
#
|
136
|
+
# @example `:strict` constructor
|
137
|
+
# class User < Dry::Struct
|
138
|
+
# constructor_type :strict
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# User.new(name: "Jane")
|
142
|
+
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
143
|
+
# User.new(age: 31)
|
144
|
+
# #=> Dry::Struct::Error: [User.new] :name is missing in Hash input
|
145
|
+
# User.new(name: nil, age: 31)
|
146
|
+
# #=> Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
|
147
|
+
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
148
|
+
# #=> Dry::Struct::Error: [User.new] unexpected keys [:unexpected] in Hash input
|
149
|
+
#
|
150
|
+
# @example `:strict_with_defaults` constructor
|
151
|
+
# class User < Dry::Struct
|
152
|
+
# constructor_type :strict_with_defaults
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# User.new(name: "Jane")
|
156
|
+
# #=> Dry::Struct::Error: [User.new] :age is missing in Hash input
|
157
|
+
# User.new(age: 31)
|
158
|
+
# #=> #<User name="John Doe" age=31>
|
159
|
+
# User.new(name: nil, age: 31)
|
160
|
+
# #=> Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
|
161
|
+
# User.new(name: "Jane", age: 31, unexpected: "attribute")
|
162
|
+
# #=> Dry::Struct::Error: [User.new] unexpected keys [:unexpected] in Hash input
|
163
|
+
#
|
164
|
+
# @see http://dry-rb.org/gems/dry-types/hash-schemas
|
165
|
+
#
|
166
|
+
# @overload constructor_type(type)
|
167
|
+
# Sets the constructor type for {Struct}
|
168
|
+
# @param [Symbol] type one of constructor types, see above
|
169
|
+
# @return [Symbol]
|
170
|
+
#
|
171
|
+
# @overload constructor_type
|
172
|
+
# Returns the constructor type for {Struct}
|
173
|
+
# @return [Symbol] (:strict)
|
174
|
+
defines :constructor_type
|
175
|
+
constructor_type :permissive
|
176
|
+
|
177
|
+
# @return [Dry::Equalizer]
|
178
|
+
defines :equalizer
|
179
|
+
|
180
|
+
# @param [Hash, #each] attributes
|
14
181
|
def initialize(attributes)
|
15
182
|
attributes.each { |key, value| instance_variable_set("@#{key}", value) }
|
16
183
|
end
|
17
184
|
|
185
|
+
# Retrieves value of previously defined attribute by its' `name`
|
186
|
+
#
|
187
|
+
# @param [String] name
|
188
|
+
# @return [Object]
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# class Book < Dry::Struct
|
192
|
+
# attribute :title, Types::Strict::String
|
193
|
+
# attribute :subtitle, Types::Strict::String.optional
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# rom_n_roda = Book.new(
|
197
|
+
# title: 'Web Development with ROM and Roda',
|
198
|
+
# subtitle: nil
|
199
|
+
# )
|
200
|
+
# rom_n_roda[:title] #=> 'Web Development with ROM and Roda'
|
201
|
+
# rom_n_roda[:subtitle] #=> nil
|
18
202
|
def [](name)
|
19
203
|
public_send(name)
|
20
204
|
end
|
21
205
|
|
206
|
+
# Converts the {Dry::Struct} to a hash with keys representing
|
207
|
+
# each attribute (as symbols) and their corresponding values
|
208
|
+
#
|
209
|
+
# @return [Hash{Symbol => Object}]
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# class Book < Dry::Struct
|
213
|
+
# attribute :title, Types::Strict::String
|
214
|
+
# attribute :subtitle, Types::Strict::String.optional
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# rom_n_roda = Book.new(
|
218
|
+
# title: 'Web Development with ROM and Roda',
|
219
|
+
# subtitle: nil
|
220
|
+
# )
|
221
|
+
# rom_n_roda.to_hash
|
222
|
+
# #=> {title: 'Web Development with ROM and Roda', subtitle: nil}
|
22
223
|
def to_hash
|
23
224
|
self.class.schema.keys.each_with_object({}) do |key, result|
|
24
225
|
result[key] = Hashify[self[key]]
|
25
226
|
end
|
26
227
|
end
|
27
228
|
alias_method :to_h, :to_hash
|
229
|
+
|
230
|
+
def new(changeset)
|
231
|
+
self.class[to_hash.merge(changeset)]
|
232
|
+
end
|
28
233
|
end
|
29
234
|
end
|
30
235
|
|
@@ -1,48 +1,78 @@
|
|
1
|
+
require 'dry/core/class_attributes'
|
2
|
+
require 'dry/equalizer'
|
3
|
+
|
1
4
|
require 'dry/struct/errors'
|
2
5
|
|
3
6
|
module Dry
|
4
7
|
class Struct
|
8
|
+
# Class-level interface of {Struct} and {Value}
|
5
9
|
module ClassInterface
|
6
|
-
include
|
7
|
-
|
8
|
-
attr_accessor :constructor
|
10
|
+
include Core::ClassAttributes
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
attr_writer :constructor_type
|
13
|
-
|
14
|
-
protected :constructor=, :equalizer=, :constructor_type=
|
12
|
+
include Dry::Types::Builder
|
15
13
|
|
14
|
+
# @param [Module] base
|
16
15
|
def self.extended(base)
|
17
|
-
base.instance_variable_set(:@schema,
|
16
|
+
base.instance_variable_set(:@schema, EMPTY_HASH)
|
18
17
|
end
|
19
18
|
|
19
|
+
# @param [Class] klass
|
20
20
|
def inherited(klass)
|
21
21
|
super
|
22
22
|
|
23
|
-
klass.instance_variable_set(:@schema,
|
24
|
-
klass.equalizer
|
25
|
-
klass.constructor_type = constructor_type
|
23
|
+
klass.instance_variable_set(:@schema, EMPTY_HASH)
|
24
|
+
klass.equalizer Equalizer.new(*schema.keys)
|
26
25
|
klass.send(:include, klass.equalizer)
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
klass.attributes(EMPTY_HASH) unless equal?(Struct)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds an attribute for this {Struct} with given `name` and `type`
|
31
|
+
# and modifies {.schema} accordingly.
|
32
|
+
#
|
33
|
+
# @param [Symbol] name name of the defined attribute
|
34
|
+
# @param [Dry::Types::Definition] type
|
35
|
+
# @return [Dry::Struct]
|
36
|
+
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
37
|
+
# same name as previously defined one
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# class Language < Dry::Struct
|
41
|
+
# attribute :name, Types::String
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Language.schema
|
45
|
+
# #=> {name: #<Dry::Types::Definition primitive=String options={}>}
|
46
|
+
#
|
47
|
+
# ruby = Language.new(name: 'Ruby')
|
48
|
+
# ruby.name #=> 'Ruby'
|
35
49
|
def attribute(name, type)
|
36
50
|
attributes(name => type)
|
37
51
|
end
|
38
52
|
|
53
|
+
# @param [Hash{Symbol => Dry::Types::Definition}] new_schema
|
54
|
+
# @return [Dry::Struct]
|
55
|
+
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
56
|
+
# same name as previously defined one
|
57
|
+
# @see #attribute
|
58
|
+
# @example
|
59
|
+
# class Book1 < Dry::Struct
|
60
|
+
# attributes(
|
61
|
+
# title: Types::String,
|
62
|
+
# author: Types::String
|
63
|
+
# )
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# Book.schema
|
67
|
+
# #=> {title: #<Dry::Types::Definition primitive=String options={}>,
|
68
|
+
# # author: #<Dry::Types::Definition primitive=String options={}>}
|
39
69
|
def attributes(new_schema)
|
40
70
|
check_schema_duplication(new_schema)
|
41
71
|
|
42
72
|
prev_schema = schema
|
43
73
|
|
44
74
|
@schema = prev_schema.merge(new_schema)
|
45
|
-
|
75
|
+
input Types['coercible.hash'].public_send(constructor_type, schema)
|
46
76
|
|
47
77
|
attr_reader(*new_schema.keys)
|
48
78
|
equalizer.instance_variable_get('@keys').concat(new_schema.keys)
|
@@ -50,6 +80,9 @@ module Dry
|
|
50
80
|
self
|
51
81
|
end
|
52
82
|
|
83
|
+
# @param [Hash{Symbol => Dry::Types::Definition, Dry::Struct}] new_schema
|
84
|
+
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
85
|
+
# same name as previously defined one
|
53
86
|
def check_schema_duplication(new_schema)
|
54
87
|
shared_keys = new_schema.keys & (schema.keys - superclass.schema.keys)
|
55
88
|
|
@@ -57,37 +90,60 @@ module Dry
|
|
57
90
|
end
|
58
91
|
private :check_schema_duplication
|
59
92
|
|
60
|
-
|
61
|
-
if type
|
62
|
-
@constructor_type = type
|
63
|
-
else
|
64
|
-
@constructor_type || :strict
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
93
|
+
# @return [Hash{Symbol => Dry::Types::Definition, Dry::Struct}]
|
68
94
|
def schema
|
69
|
-
super_schema = superclass.respond_to?(:schema) ? superclass.schema :
|
95
|
+
super_schema = superclass.respond_to?(:schema) ? superclass.schema : EMPTY_HASH
|
70
96
|
super_schema.merge(@schema)
|
71
97
|
end
|
72
98
|
|
99
|
+
# @param [Hash{Symbol => Object},Dry::Struct] attributes
|
100
|
+
# @raise [Struct::Error] if the given attributes don't conform {#schema}
|
101
|
+
# with given {#constructor_type}
|
73
102
|
def new(attributes = default_attributes)
|
74
103
|
if attributes.instance_of?(self)
|
75
104
|
attributes
|
76
105
|
else
|
77
|
-
super(
|
106
|
+
super(input[attributes])
|
78
107
|
end
|
79
108
|
rescue Types::SchemaError, Types::MissingKeyError, Types::UnknownKeysError => error
|
80
109
|
raise Struct::Error, "[#{self}.new] #{error}"
|
81
110
|
end
|
82
|
-
alias_method :call, :new
|
83
|
-
alias_method :[], :new
|
84
111
|
|
112
|
+
# Calls type constructor. The behavior is identical to `.new` but returns
|
113
|
+
# returns the input back if it's a subclass of the struct.
|
114
|
+
#
|
115
|
+
# @param [Hash{Symbol => Object},Dry::Struct] attributes
|
116
|
+
# @return [Dry::Struct]
|
117
|
+
def call(attributes = default_attributes)
|
118
|
+
return attributes if attributes.is_a?(self)
|
119
|
+
new(attributes)
|
120
|
+
end
|
121
|
+
alias_method :[], :call
|
122
|
+
|
123
|
+
# Retrieves default attributes from defined {.schema}.
|
124
|
+
# Used in a {Struct} constructor if no attributes provided to {.new}
|
125
|
+
#
|
126
|
+
# @return [Hash{Symbol => Object}]
|
85
127
|
def default_attributes
|
128
|
+
check_invalid_schema_keys
|
86
129
|
schema.each_with_object({}) { |(name, type), result|
|
87
|
-
result[name] = type.
|
130
|
+
result[name] = type.evaluate if type.default?
|
88
131
|
}
|
89
132
|
end
|
90
133
|
|
134
|
+
def check_invalid_schema_keys
|
135
|
+
invalid_keys = schema.select { |name, type| type.instance_of?(String) }
|
136
|
+
raise ArgumentError, argument_error_msg(invalid_keys.keys) if invalid_keys.any?
|
137
|
+
end
|
138
|
+
|
139
|
+
def argument_error_msg(keys)
|
140
|
+
"Invaild argument for #{keys.join(', ')}"
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param [Hash{Symbol => Object}] input
|
144
|
+
# @yieldparam [Dry::Types::Result::Failure] failure
|
145
|
+
# @yieldreturn [Dry::Types::ResultResult]
|
146
|
+
# @return [Dry::Types::Result]
|
91
147
|
def try(input)
|
92
148
|
Types::Result::Success.new(self[input])
|
93
149
|
rescue Struct::Error => e
|
@@ -95,33 +151,49 @@ module Dry
|
|
95
151
|
block_given? ? yield(failure) : failure
|
96
152
|
end
|
97
153
|
|
154
|
+
# @param [({Symbol => Object})] args
|
155
|
+
# @return [Dry::Types::Result::Success]
|
98
156
|
def success(*args)
|
99
157
|
result(Types::Result::Success, *args)
|
100
158
|
end
|
101
159
|
|
160
|
+
# @param [({Symbol => Object})] args
|
161
|
+
# @return [Dry::Types::Result::Failure]
|
102
162
|
def failure(*args)
|
103
163
|
result(Types::Result::Failure, *args)
|
104
164
|
end
|
105
165
|
|
166
|
+
# @param [Class] klass
|
167
|
+
# @param [({Symbol => Object})] args
|
106
168
|
def result(klass, *args)
|
107
169
|
klass.new(*args)
|
108
170
|
end
|
109
171
|
|
172
|
+
# @return [false]
|
110
173
|
def default?
|
111
174
|
false
|
112
175
|
end
|
113
176
|
|
177
|
+
# @param [Object, Dry::Struct] value
|
178
|
+
# @return [Boolean]
|
114
179
|
def valid?(value)
|
115
180
|
self === value
|
116
181
|
end
|
117
182
|
|
183
|
+
# @return [true]
|
118
184
|
def constrained?
|
119
185
|
true
|
120
186
|
end
|
121
187
|
|
188
|
+
# @return [self]
|
122
189
|
def primitive
|
123
190
|
self
|
124
191
|
end
|
192
|
+
|
193
|
+
# @return [false]
|
194
|
+
def optional?
|
195
|
+
false
|
196
|
+
end
|
125
197
|
end
|
126
198
|
end
|
127
199
|
end
|
data/lib/dry/struct/errors.rb
CHANGED
@@ -4,9 +4,13 @@ module Dry
|
|
4
4
|
|
5
5
|
setting :namespace, self
|
6
6
|
|
7
|
+
# Raised when given input doesn't conform schema and constructor type
|
7
8
|
Error = Class.new(TypeError)
|
8
9
|
|
10
|
+
# Raised when defining duplicate attributes
|
9
11
|
class RepeatedAttributeError < ArgumentError
|
12
|
+
# @param [Symbol] key
|
13
|
+
# attribute name that is the same as previously defined one
|
10
14
|
def initialize(key)
|
11
15
|
super("Attribute :#{key} has already been defined")
|
12
16
|
end
|
data/lib/dry/struct/hashify.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
# Converts value to hash recursively
|
2
|
-
|
3
1
|
module Dry
|
4
2
|
class Struct
|
3
|
+
# Helper for {Struct#to_hash} implementation
|
5
4
|
module Hashify
|
5
|
+
# Converts value to hash recursively
|
6
|
+
# @param [#to_hash, #map, Object] value
|
7
|
+
# @return [Hash, Array]
|
6
8
|
def self.[](value)
|
7
9
|
if value.respond_to?(:to_hash)
|
8
10
|
value.to_hash
|
data/lib/dry/struct/value.rb
CHANGED
@@ -2,7 +2,27 @@ require 'ice_nine'
|
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
class Struct
|
5
|
+
# {Value} objects behave like {Struct}s but *deeply frozen*
|
6
|
+
# using [`ice_nine`](https://github.com/dkubb/ice_nine)
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class Location < Dry::Struct::Value
|
10
|
+
# attribute :lat, Types::Strict::Float
|
11
|
+
# attribute :lng, Types::Strict::Float
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# loc1 = Location.new(lat: 1.23, lng: 4.56)
|
15
|
+
# loc2 = Location.new(lat: 1.23, lng: 4.56)
|
16
|
+
#
|
17
|
+
# loc1.frozen? #=> true
|
18
|
+
# loc2.frozen? #=> true
|
19
|
+
# loc1 == loc2 #=> true
|
20
|
+
#
|
21
|
+
# @see https://github.com/dkubb/ice_nine
|
5
22
|
class Value < self
|
23
|
+
# @param (see ClassInterface#new)
|
24
|
+
# @return [Value]
|
25
|
+
# @see https://github.com/dkubb/ice_nine
|
6
26
|
def self.new(*)
|
7
27
|
IceNine.deep_freeze(super)
|
8
28
|
end
|
data/lib/dry/struct/version.rb
CHANGED
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.3.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: 2017-
|
11
|
+
date: 2017-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-equalizer
|
@@ -58,6 +58,20 @@ dependencies:
|
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: 0.9.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: dry-core
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.3'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.3'
|
61
75
|
- !ruby/object:Gem::Dependency
|
62
76
|
name: ice_nine
|
63
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,6 +128,20 @@ dependencies:
|
|
114
128
|
- - "~>"
|
115
129
|
- !ruby/object:Gem::Version
|
116
130
|
version: '3.3'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: yard
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 0.9.5
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 0.9.5
|
117
145
|
description: Typed structs and value objects.
|
118
146
|
email:
|
119
147
|
- piotr.solnica@gmail.com
|
@@ -124,6 +152,7 @@ files:
|
|
124
152
|
- ".gitignore"
|
125
153
|
- ".rspec"
|
126
154
|
- ".travis.yml"
|
155
|
+
- ".yardopts"
|
127
156
|
- CHANGELOG.md
|
128
157
|
- Gemfile
|
129
158
|
- LICENSE
|
@@ -162,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
191
|
version: '0'
|
163
192
|
requirements: []
|
164
193
|
rubyforge_project:
|
165
|
-
rubygems_version: 2.6.
|
194
|
+
rubygems_version: 2.6.11
|
166
195
|
signing_key:
|
167
196
|
specification_version: 4
|
168
197
|
summary: Typed structs and value objects.
|