value_semantics 1.0.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d5af9c70cace0213a309865f7fb2eaf09759d3f654d530d577d9067ee8e025d
4
- data.tar.gz: 31ef2d58e797173040691bc6ac57703751102c40519c8bcd4913f6fa24e97481
3
+ metadata.gz: 235fbd4782ce1bc943f12688408c20a5b6893e7b57882c63b725953115de74d3
4
+ data.tar.gz: 6adbdbcc36d8093fd323fb2d734557025df4b0dc9983e6ba343690a229dfed40
5
5
  SHA512:
6
- metadata.gz: 051b338a932d31e4e74a2699bd45a9a740e7fe8673f8833e566254c4be8262f90947160784db895d1706c40a8f910db21ea0999a90a3df6d65481dd60932a626
7
- data.tar.gz: 8860483b155c84402fc6964967b29ba1cef4f6a1b46409752b714d139c78625b02d0bcf184a4eb671bb2b94c4b4442938fcacec32e07f854f934e2defb87e811
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
- To implement coercion, define a class method called `coerce_#{attr}` which
170
- accepts a raw value, and returns the coerced value.
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
- If coercion is not possible, you can return the value unchanged,
199
- allowing the validator to fail.
200
- Another option is to raise an error within the coercion method.
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:
@@ -75,19 +75,21 @@ module ValueSemantics
75
75
  end
76
76
 
77
77
  class Attribute
78
- attr_reader :name, :has_default, :default_value, :validator
78
+ NOT_SPECIFIED = Object.new.freeze
79
79
 
80
- def initialize(name:, has_default:, default_value:, validator:)
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 has_default
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
- if klass.respond_to?(coercion_method)
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 has_default
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
 
@@ -1,3 +1,3 @@
1
1
  module ValueSemantics
2
- VERSION = "1.0.1"
2
+ VERSION = "2.0.1"
3
3
  end
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: 1.0.1
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-09-09 00:00:00.000000000 Z
11
+ date: 2018-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler