value_semantics 2.0.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 235fbd4782ce1bc943f12688408c20a5b6893e7b57882c63b725953115de74d3
4
- data.tar.gz: 6adbdbcc36d8093fd323fb2d734557025df4b0dc9983e6ba343690a229dfed40
3
+ metadata.gz: 17ee24bc8ffd744493e9957a4cb7ee0071f0c33aa8560b3ed36689575b9c0ab1
4
+ data.tar.gz: 05da8d260966f9257578f0ebf3891ba447b51032709bb75b39bdacba3e081961
5
5
  SHA512:
6
- metadata.gz: 179eed3a45a75b8f6546c99fd4fdcd2cb987885a4b00f36caa359a3e7b19854dd0401d3b6f3c4cd5b108256492be94d068dfa1765eb6a6844b9ccd610212cffe
7
- data.tar.gz: c9eefd7b84e63507513c2d3b88469730c346899a2a7ae109bc1159f2ed764d71102a7dbbd9e7ae0ea9d2caad1ee82c1c6b740c69eb508caa50571687d0f51c75
6
+ metadata.gz: 4a4da5150969146f5fd8cb06a17f8e43b1c897cd40f228cdddd7b2a8367fdbbceeacc66843668bf74a48575d72f752d3f24fd101c2ac7e946335688a1eec95a3
7
+ data.tar.gz: c0e71a37be6df7bcafa4833f7b7cea4304468818a4391e020770c6f85e16edaf68a56c84c572f3bd26399aa85b2dc149abb48660d82750a656d746d87da5a16a
data/README.md CHANGED
@@ -16,8 +16,42 @@ Invalid or missing attributes cause an exception intended for developers,
16
16
  not an error message intended for the user.
17
17
 
18
18
 
19
- Basic Usage
20
- -----------
19
+ Defining and Creating Value Objects
20
+ -----------------------------------
21
+
22
+ ```ruby
23
+ class Person
24
+ include ValueSemantics.for_attributes {
25
+ name String, default: "Anon Emous"
26
+ birthday Either(Date, nil), coerce: true
27
+ }
28
+
29
+ def self.coerce_birthday(value)
30
+ if value.is_a?(String)
31
+ Date.parse(value)
32
+ else
33
+ value
34
+ end
35
+ end
36
+ end
37
+
38
+ Person.new(name: "Tom", birthday: "2020-12-25")
39
+ #=> #<Person name="Tom" birthday=#<Date: 2020-12-25 ((2459209j,0s,0n),+0s,2299161j)>>
40
+
41
+ Person.new(birthday: Date.today)
42
+ #=> #<Person name="Anon Emous" birthday=#<Date: 2018-09-04 ((2458366j,0s,0n),+0s,2299161j)>>
43
+
44
+ Person.new(birthday: nil)
45
+ #=> #<Person name="Anon Emous" birthday=nil>
46
+ ```
47
+
48
+ The curly bracket syntax used with `ValueSemantics.for_attributes` is, unfortunately,
49
+ mandatory due to Ruby's precedence rules.
50
+ The `do`/`end` syntax will not work unless you surround the whole thing with parenthesis.
51
+
52
+
53
+ Using Value Objects
54
+ -------------------
21
55
 
22
56
  ```ruby
23
57
  require 'value_semantics'
@@ -64,9 +98,31 @@ tom.eql?(other_tom) #=> true
64
98
  tom.hash == other_tom.hash #=> true
65
99
  ```
66
100
 
67
- The curly bracket syntax used with `ValueSemantics.for_attributes` is, unfortunately,
68
- mandatory due to Ruby's precedence rules.
69
- The `do`/`end` syntax will not work unless you surround the whole thing with parenthesis.
101
+
102
+ Defaults
103
+ --------
104
+
105
+ Defaults can be specified in one of two ways:
106
+ the `:default` option, or the `default_generator` option.
107
+
108
+ ```ruby
109
+ class Cat
110
+ include ValueSemantics.for_attributes {
111
+ paws Integer, default: 4
112
+ born_at Time, default_generator: ->{ Time.now }
113
+ }
114
+ end
115
+
116
+ Cat.new
117
+ #=> #<Cat paws=4 born_at=2018-12-21 18:42:01 +1100>
118
+ ```
119
+
120
+ The `default` option is a single value.
121
+
122
+ The `default_generator` option is a callable object, which returns a default value.
123
+ In the example above, `default_generator` is a lambda that returns the current time.
124
+
125
+ Only one of these options can be used per attribute.
70
126
 
71
127
 
72
128
  Validation (Types)
@@ -83,7 +139,7 @@ Anything that you can use in a `case`/`when` expression will work.
83
139
  class Person
84
140
  include ValueSemantics.for_attributes {
85
141
  name String
86
- birthday /\d\d\d\d-\d\d-\d\d/
142
+ birthday /\d{4}-\d{2}-\d{2}/
87
143
  }
88
144
  end
89
145
 
@@ -243,34 +299,6 @@ For example, the default value could be a string,
243
299
  which would then be coerced into an `IPAddr` object.
244
300
 
245
301
 
246
- ## All Together
247
-
248
- ```ruby
249
- class Person
250
- include ValueSemantics.for_attributes {
251
- name String, default: "Anon Emous"
252
- birthday Either(Date, nil), coerce: true
253
- }
254
-
255
- def self.coerce_birthday(value)
256
- if value.is_a?(String)
257
- Date.parse(value)
258
- else
259
- value
260
- end
261
- end
262
- end
263
-
264
- Person.new(name: "Tom", birthday: "2020-12-25")
265
- #=> #<Person name="Tom" birthday=#<Date: 2020-12-25 ((2459209j,0s,0n),+0s,2299161j)>>
266
-
267
- Person.new(birthday: Date.today)
268
- #=> #<Person name="Anon Emous" birthday=#<Date: 2018-09-04 ((2458366j,0s,0n),+0s,2299161j)>>
269
-
270
- Person.new(birthday: nil)
271
- #=> #<Person name="Anon Emous" birthday=nil>
272
- ```
273
-
274
302
  ## Installation
275
303
 
276
304
  Add this line to your application's Gemfile:
@@ -288,10 +316,6 @@ Or install it yourself as:
288
316
  $ gem install value_semantics
289
317
 
290
318
 
291
- ## TODO
292
-
293
- - Allow defaults to be generated by calling a method (e.g. to get the current time)
294
-
295
319
  ## Contributing
296
320
 
297
321
  Bug reports and pull requests are welcome on GitHub at:
@@ -1,4 +1,6 @@
1
1
  module ValueSemantics
2
+ NOT_SPECIFIED = Object.new.freeze
3
+
2
4
  def self.for_attributes(&block)
3
5
  attributes = DSL.run(&block)
4
6
  generate_module(attributes.freeze)
@@ -75,13 +77,13 @@ module ValueSemantics
75
77
  end
76
78
 
77
79
  class Attribute
78
- NOT_SPECIFIED = Object.new.freeze
80
+ NO_DEFAULT_GENERATOR = ->{ raise "Attribute does not have a default value" }
79
81
 
80
- attr_reader :name, :validator, :coercer
82
+ attr_reader :name, :validator, :coercer, :default_generator
81
83
 
82
- def initialize(name:, default_value:, validator:, coercer:)
84
+ def initialize(name:, default_generator:, validator:, coercer:)
83
85
  @name = name.to_sym
84
- @default_value = default_value
86
+ @default_generator = default_generator
85
87
  @validator = validator
86
88
  @coercer = coercer
87
89
  freeze
@@ -89,10 +91,10 @@ module ValueSemantics
89
91
 
90
92
  def determine_from!(attr_hash, klass)
91
93
  raw_value = attr_hash.fetch(name) do
92
- if default_specified?
93
- default_value
94
- else
94
+ if default_generator.equal?(NO_DEFAULT_GENERATOR)
95
95
  raise ArgumentError, "Value missing for attribute '#{name}'"
96
+ else
97
+ default_generator.call
96
98
  end
97
99
  end
98
100
 
@@ -115,18 +117,6 @@ module ValueSemantics
115
117
  end
116
118
  end
117
119
 
118
- def default_specified?
119
- !@default_value.equal?(NOT_SPECIFIED)
120
- end
121
-
122
- def default_value
123
- if default_specified?
124
- @default_value
125
- else
126
- fail "Attribute does not have a default value"
127
- end
128
- end
129
-
130
120
  def validate?(value)
131
121
  validator === value
132
122
  end
@@ -169,11 +159,28 @@ module ValueSemantics
169
159
  ArrayOf.new(element_validator)
170
160
  end
171
161
 
172
- def def_attr(attr_name, validator=Anything, default: Attribute::NOT_SPECIFIED, coerce: nil)
162
+ def def_attr(attr_name,
163
+ validator=Anything,
164
+ default: NOT_SPECIFIED,
165
+ default_generator: nil,
166
+ coerce: nil
167
+ )
168
+ generator = begin
169
+ if default_generator && !default.equal?(NOT_SPECIFIED)
170
+ raise ArgumentError, "Attribute '#{attr_name}' can not have both a default, and a default_generator"
171
+ elsif default_generator
172
+ default_generator
173
+ elsif !default.equal?(NOT_SPECIFIED)
174
+ ->{ default }
175
+ else
176
+ Attribute::NO_DEFAULT_GENERATOR
177
+ end
178
+ end
179
+
173
180
  __attributes << Attribute.new(
174
181
  name: attr_name,
175
182
  validator: validator,
176
- default_value: default,
183
+ default_generator: generator,
177
184
  coercer: coerce
178
185
  )
179
186
  end
@@ -1,3 +1,3 @@
1
1
  module ValueSemantics
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.0"
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: 2.0.1
4
+ version: 2.1.0
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-10-15 00:00:00.000000000 Z
11
+ date: 2018-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -121,8 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  - !ruby/object:Gem::Version
122
122
  version: '0'
123
123
  requirements: []
124
- rubyforge_project:
125
- rubygems_version: 2.7.7
124
+ rubygems_version: 3.0.0
126
125
  signing_key:
127
126
  specification_version: 4
128
127
  summary: Create value classes quickly, with all the proper conventions.