value_semantics 1.0.1 → 2.0.1
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/README.md +51 -8
- data/lib/value_semantics.rb +18 -12
- data/lib/value_semantics/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 235fbd4782ce1bc943f12688408c20a5b6893e7b57882c63b725953115de74d3
|
4
|
+
data.tar.gz: 6adbdbcc36d8093fd323fb2d734557025df4b0dc9983e6ba343690a229dfed40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 179eed3a45a75b8f6546c99fd4fdcd2cb987885a4b00f36caa359a3e7b19854dd0401d3b6f3c4cd5b108256492be94d068dfa1765eb6a6844b9ccd610212cffe
|
7
|
+
data.tar.gz: c9eefd7b84e63507513c2d3b88469730c346899a2a7ae109bc1159f2ed764d71102a7dbbd9e7ae0ea9d2caad1ee82c1c6b740c69eb508caa50571687d0f51c75
|
data/README.md
CHANGED
@@ -166,13 +166,14 @@ proper, valid values, where possible.
|
|
166
166
|
For example, an object with an `IPAddr` attribute may allow string values,
|
167
167
|
which are then coerced into `IPAddr` objects.
|
168
168
|
|
169
|
-
|
170
|
-
|
169
|
+
Using the option `coerce: true`,
|
170
|
+
coercion happens through a custom class method called `coerce_#{attr}`,
|
171
|
+
which takes the raw value as an argument, and returns the coerced value.
|
171
172
|
|
172
173
|
```ruby
|
173
174
|
class Server
|
174
175
|
include ValueSemantics.for_attributes {
|
175
|
-
address IPAddr
|
176
|
+
address IPAddr, coerce: true
|
176
177
|
}
|
177
178
|
|
178
179
|
def self.coerce_address(value)
|
@@ -195,12 +196,51 @@ Server.new(address: 42)
|
|
195
196
|
#=> Value for attribute 'address' is not valid: 42
|
196
197
|
```
|
197
198
|
|
198
|
-
|
199
|
-
|
200
|
-
|
199
|
+
You can also use any callable object as a coercer.
|
200
|
+
That means, you could use a lambda:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
class Server
|
204
|
+
include ValueSemantics.for_attributes {
|
205
|
+
address IPAddr, coerce: ->(value) { IPAddr.new(value) }
|
206
|
+
}
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
Or a custom class:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
class MyAddressCoercer
|
214
|
+
def call(value)
|
215
|
+
IPAddr.new(value)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
class Server
|
220
|
+
include ValueSemantics.for_attributes {
|
221
|
+
address IPAddr, coerce: MyAddressCoercer.new
|
222
|
+
}
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
Or reuse an existing class method:
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
class Server
|
230
|
+
include ValueSemantics.for_attributes {
|
231
|
+
address IPAddr, coerce: IPAddr.method(:new)
|
232
|
+
}
|
233
|
+
end
|
234
|
+
```
|
201
235
|
|
202
236
|
Coercion happens before validation.
|
237
|
+
If coercion is not possible, coercers can return the raw value unchanged,
|
238
|
+
allowing the validator to fail with a nice, descriptive exception.
|
239
|
+
Another option is to raise an error within the coercion method.
|
240
|
+
|
203
241
|
Default attribute values also pass through coercion.
|
242
|
+
For example, the default value could be a string,
|
243
|
+
which would then be coerced into an `IPAddr` object.
|
204
244
|
|
205
245
|
|
206
246
|
## All Together
|
@@ -209,7 +249,7 @@ Default attribute values also pass through coercion.
|
|
209
249
|
class Person
|
210
250
|
include ValueSemantics.for_attributes {
|
211
251
|
name String, default: "Anon Emous"
|
212
|
-
birthday Either(Date, nil)
|
252
|
+
birthday Either(Date, nil), coerce: true
|
213
253
|
}
|
214
254
|
|
215
255
|
def self.coerce_birthday(value)
|
@@ -231,7 +271,6 @@ Person.new(birthday: nil)
|
|
231
271
|
#=> #<Person name="Anon Emous" birthday=nil>
|
232
272
|
```
|
233
273
|
|
234
|
-
|
235
274
|
## Installation
|
236
275
|
|
237
276
|
Add this line to your application's Gemfile:
|
@@ -249,6 +288,10 @@ Or install it yourself as:
|
|
249
288
|
$ gem install value_semantics
|
250
289
|
|
251
290
|
|
291
|
+
## TODO
|
292
|
+
|
293
|
+
- Allow defaults to be generated by calling a method (e.g. to get the current time)
|
294
|
+
|
252
295
|
## Contributing
|
253
296
|
|
254
297
|
Bug reports and pull requests are welcome on GitHub at:
|
data/lib/value_semantics.rb
CHANGED
@@ -75,19 +75,21 @@ module ValueSemantics
|
|
75
75
|
end
|
76
76
|
|
77
77
|
class Attribute
|
78
|
-
|
78
|
+
NOT_SPECIFIED = Object.new.freeze
|
79
79
|
|
80
|
-
|
80
|
+
attr_reader :name, :validator, :coercer
|
81
|
+
|
82
|
+
def initialize(name:, default_value:, validator:, coercer:)
|
81
83
|
@name = name.to_sym
|
82
|
-
@has_default = has_default
|
83
84
|
@default_value = default_value
|
84
85
|
@validator = validator
|
86
|
+
@coercer = coercer
|
85
87
|
freeze
|
86
88
|
end
|
87
89
|
|
88
90
|
def determine_from!(attr_hash, klass)
|
89
91
|
raw_value = attr_hash.fetch(name) do
|
90
|
-
if
|
92
|
+
if default_specified?
|
91
93
|
default_value
|
92
94
|
else
|
93
95
|
raise ArgumentError, "Value missing for attribute '#{name}'"
|
@@ -104,15 +106,21 @@ module ValueSemantics
|
|
104
106
|
end
|
105
107
|
|
106
108
|
def coerce(attr_value, klass)
|
107
|
-
|
109
|
+
return attr_value unless coercer # coercion not enabled
|
110
|
+
|
111
|
+
if coercer.equal?(true)
|
108
112
|
klass.public_send(coercion_method, attr_value)
|
109
113
|
else
|
110
|
-
attr_value
|
114
|
+
coercer.call(attr_value)
|
111
115
|
end
|
112
116
|
end
|
113
117
|
|
118
|
+
def default_specified?
|
119
|
+
!@default_value.equal?(NOT_SPECIFIED)
|
120
|
+
end
|
121
|
+
|
114
122
|
def default_value
|
115
|
-
if
|
123
|
+
if default_specified?
|
116
124
|
@default_value
|
117
125
|
else
|
118
126
|
fail "Attribute does not have a default value"
|
@@ -133,8 +141,6 @@ module ValueSemantics
|
|
133
141
|
end
|
134
142
|
|
135
143
|
class DSL
|
136
|
-
NOT_SPECIFIED = Object.new
|
137
|
-
|
138
144
|
def self.run(&block)
|
139
145
|
dsl = new
|
140
146
|
dsl.instance_eval(&block)
|
@@ -163,12 +169,12 @@ module ValueSemantics
|
|
163
169
|
ArrayOf.new(element_validator)
|
164
170
|
end
|
165
171
|
|
166
|
-
def def_attr(attr_name, validator=Anything, default: NOT_SPECIFIED)
|
172
|
+
def def_attr(attr_name, validator=Anything, default: Attribute::NOT_SPECIFIED, coerce: nil)
|
167
173
|
__attributes << Attribute.new(
|
168
174
|
name: attr_name,
|
169
|
-
has_default: !(NOT_SPECIFIED.equal?(default)),
|
170
|
-
default_value: default,
|
171
175
|
validator: validator,
|
176
|
+
default_value: default,
|
177
|
+
coercer: coerce
|
172
178
|
)
|
173
179
|
end
|
174
180
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: value_semantics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|