dry-struct 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|