value_semantics 3.0.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +115 -44
- data/lib/value_semantics.rb +109 -16
- data/lib/value_semantics/monkey_patched.rb +3 -0
- data/lib/value_semantics/version.rb +1 -1
- metadata +29 -18
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.travis.yml +0 -24
- data/Gemfile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/test +0 -19
- data/value_semantics.gemspec +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e86c0e4467ff36d89870545718de723b0a0b62ff21dc50d30a3f6e1c0766c4c1
|
4
|
+
data.tar.gz: '011313548dfe90ee7b686bc91338b5e0f403ce2dd26f98fd607fdf16ef2c5ce7'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecd428749dd01e806df9bbd4a361084f1b066298d464fea3a04ff7f66214ba224b99b93c7902f513c1c4f89fc52690779f4a14bb2c7e78106a212456a8f2c14d
|
7
|
+
data.tar.gz: 86c31d6565594493891581dde7feac4f9a9a5c1ff39b86874fd1be74d6bf3827b9a3311369dcaf9a76d9c41ca03be9abc464736ac86d09d316125e9472534554
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
Notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [3.4.0] - 2020-08-01
|
9
|
+
### Added
|
10
|
+
- Value objects can be instantiated from any object that responds to `#to_h`.
|
11
|
+
Previously attributes were required to be given as a `Hash`.
|
12
|
+
|
13
|
+
- Added monkey patching for super-convenient attribute definitions. This is
|
14
|
+
**not** available by default, and needs to be explicitly enabled with
|
15
|
+
`ValueSemantics.monkey_patch!` or `require 'value_semantics/monkey_patched'`.
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Improved exception messages for easier development experience
|
19
|
+
|
20
|
+
- Raises `ValueSemantics::InvalidValue` instead of `ArgumentError` when
|
21
|
+
attempting to initialize with an invalid value. `ValueSemantics::InvalidValue`
|
22
|
+
is a subclass of `ArgumentError`, so this change should be backward
|
23
|
+
compatible.
|
24
|
+
|
25
|
+
## [3.3.0] - 2020-07-17
|
26
|
+
### Added
|
27
|
+
- Added support for pattern matching in Ruby 2.7
|
28
|
+
|
29
|
+
## [3.2.1] - 2020-07-11
|
30
|
+
### Fixed
|
31
|
+
- Fix warnings new to Ruby 2.7 about keyword arguments
|
32
|
+
|
33
|
+
## [3.2.0] - 2019-09-30
|
34
|
+
### Added
|
35
|
+
- `ValueSemantics::Struct`, a convenience for creating a new class and mixing
|
36
|
+
in ValueSemantics in a single step.
|
37
|
+
|
38
|
+
## [3.1.0] - 2019-06-30
|
39
|
+
### Added
|
40
|
+
- Built-in PP support for value classes
|
41
|
+
|
42
|
+
## [3.0.0] - 2019-01-27
|
43
|
+
|
44
|
+
First public release
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/value_semantics.svg)](https://badge.fury.io/rb/value_semantics)
|
2
2
|
[![Build Status](https://travis-ci.org/tomdalling/value_semantics.svg?branch=master)](https://travis-ci.org/tomdalling/value_semantics)
|
3
|
-
![Mutation Coverage](https://img.shields.io/badge/mutation%20coverage-
|
3
|
+
![Mutation Coverage](https://img.shields.io/badge/mutation%20coverage-to%20the%20MAX-brightgreen.svg)
|
4
4
|
|
5
5
|
ValueSemantics
|
6
6
|
==============
|
@@ -15,6 +15,16 @@ These are intended for internal use, as opposed to validating user input like Ac
|
|
15
15
|
Invalid or missing attributes cause an exception for developers,
|
16
16
|
not an error message intended for application users.
|
17
17
|
|
18
|
+
See:
|
19
|
+
|
20
|
+
- The [announcement blog post][blog post] for some of the rationale behind the gem
|
21
|
+
- [RubyTapas episode #584][rubytapas] for an example usage scenario
|
22
|
+
- Some [discussion on Reddit][reddit]
|
23
|
+
|
24
|
+
[blog post]: https://www.rubypigeon.com/posts/value-semantics-gem-for-making-value-classes/
|
25
|
+
[rubytapas]: https://www.rubytapas.com/2019/07/09/from-hash-to-value-object/
|
26
|
+
[reddit]: https://www.reddit.com/r/ruby/comments/akz4fs/valuesemanticsa_gem_for_making_value_classes/
|
27
|
+
|
18
28
|
|
19
29
|
Defining and Creating Value Objects
|
20
30
|
-----------------------------------
|
@@ -45,9 +55,13 @@ Person.new(birthday: nil)
|
|
45
55
|
#=> #<Person name="Anon Emous" birthday=nil>
|
46
56
|
```
|
47
57
|
|
48
|
-
|
49
|
-
|
50
|
-
|
58
|
+
Value objects are typically initialized with keyword arguments or a `Hash`, but
|
59
|
+
will accept any object that responds to `#to_h`.
|
60
|
+
|
61
|
+
The curly bracket syntax used with `ValueSemantics.for_attributes` is,
|
62
|
+
unfortunately, mandatory due to Ruby's precedence rules. For a shorter
|
63
|
+
alternative method that works better with `do`/`end`, see [Convenience (Monkey
|
64
|
+
Patch)](#convenience-monkey-patch) below.
|
51
65
|
|
52
66
|
|
53
67
|
Using Value Objects
|
@@ -66,38 +80,70 @@ end
|
|
66
80
|
tom = Person.new(name: 'Tom')
|
67
81
|
|
68
82
|
|
69
|
-
#
|
70
83
|
# Read-only attributes
|
71
|
-
#
|
72
84
|
tom.name #=> "Tom"
|
73
85
|
tom.age #=> 31
|
74
86
|
|
75
87
|
|
76
|
-
#
|
77
88
|
# Convert to Hash
|
78
|
-
#
|
79
89
|
tom.to_h #=> { :name => "Tom", :age => 31 }
|
80
90
|
|
81
91
|
|
82
|
-
#
|
83
92
|
# Non-destructive updates
|
84
|
-
#
|
85
93
|
old_tom = tom.with(age: 99)
|
86
|
-
|
87
94
|
old_tom #=> #<Person name="Tom" age=99>
|
88
95
|
tom #=> #<Person name="Tom" age=31> (unchanged)
|
89
96
|
|
90
97
|
|
91
|
-
#
|
92
98
|
# Equality
|
93
|
-
#
|
94
99
|
other_tom = Person.new(name: 'Tom', age: 31)
|
95
|
-
|
96
100
|
tom == other_tom #=> true
|
97
101
|
tom.eql?(other_tom) #=> true
|
98
102
|
tom.hash == other_tom.hash #=> true
|
103
|
+
|
104
|
+
|
105
|
+
# Ruby 2.7+ pattern matching
|
106
|
+
case tom
|
107
|
+
in name: "Tom", age:
|
108
|
+
puts age # outputs: 31
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
|
113
|
+
Convenience (Monkey Patch)
|
114
|
+
--------------------------
|
115
|
+
|
116
|
+
There is a shorter way to define value attributes:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Person
|
120
|
+
value_semantics do
|
121
|
+
name String
|
122
|
+
age Integer
|
123
|
+
end
|
124
|
+
end
|
99
125
|
```
|
100
126
|
|
127
|
+
**This is disabled by default**, to avoid polluting every class with an extra
|
128
|
+
class method.
|
129
|
+
|
130
|
+
This convenience method can be enabled in two ways:
|
131
|
+
|
132
|
+
1. Add a `require:` option to your `Gemfile` like this:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
gem 'value_semantics', '~> 3.3', require: 'value_semantics/monkey_patched'
|
136
|
+
```
|
137
|
+
|
138
|
+
2. Alternatively, you can call `ValueSemantics.monkey_patch!` somewhere early
|
139
|
+
in the boot sequence of your code -- at the top of your script, for example,
|
140
|
+
or `config/boot.rb` if it's a Rails project.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
require 'value_semantics'
|
144
|
+
ValueSemantics.monkey_patch!
|
145
|
+
```
|
146
|
+
|
101
147
|
|
102
148
|
Defaults
|
103
149
|
--------
|
@@ -145,13 +191,13 @@ end
|
|
145
191
|
|
146
192
|
Person.new(name: 'Tom', ...) # works
|
147
193
|
Person.new(name: 5, ...)
|
148
|
-
#=>
|
149
|
-
#=>
|
194
|
+
#=> ValueSemantics::InvalidValue:
|
195
|
+
#=> Attribute `Person#name` is invalid: 5
|
150
196
|
|
151
197
|
Person.new(birthday: "1970-01-01", ...) # works
|
152
198
|
Person.new(birthday: "hello", ...)
|
153
|
-
#=>
|
154
|
-
#=>
|
199
|
+
#=> ValueSemantics::InvalidValue:
|
200
|
+
#=> Attribute 'Person#birthday' is invalid: "hello"
|
155
201
|
```
|
156
202
|
|
157
203
|
|
@@ -206,8 +252,8 @@ end
|
|
206
252
|
|
207
253
|
Person.new(age: 9) # works
|
208
254
|
Person.new(age: 8)
|
209
|
-
#=>
|
210
|
-
#=>
|
255
|
+
#=> ValueSemantics::InvalidValue:
|
256
|
+
#=> Attribute 'Person#age' is invalid: 8
|
211
257
|
```
|
212
258
|
|
213
259
|
Default attribute values also pass through validation.
|
@@ -219,46 +265,48 @@ Coercion
|
|
219
265
|
Coercion allows non-standard or "convenience" values to be converted into
|
220
266
|
proper, valid values, where possible.
|
221
267
|
|
222
|
-
For example, an object with an `
|
223
|
-
which are then coerced into `
|
268
|
+
For example, an object with an `Pathname` attribute may allow string values,
|
269
|
+
which are then coerced into `Pathname` objects.
|
224
270
|
|
225
271
|
Using the option `coerce: true`,
|
226
272
|
coercion happens through a custom class method called `coerce_#{attr}`,
|
227
273
|
which takes the raw value as an argument, and returns the coerced value.
|
228
274
|
|
229
275
|
```ruby
|
230
|
-
|
276
|
+
require 'pathname'
|
277
|
+
|
278
|
+
class Document
|
231
279
|
include ValueSemantics.for_attributes {
|
232
|
-
|
280
|
+
path Pathname, coerce: true
|
233
281
|
}
|
234
282
|
|
235
|
-
def self.
|
283
|
+
def self.coerce_path(value)
|
236
284
|
if value.is_a?(String)
|
237
|
-
|
285
|
+
Pathname.new(value)
|
238
286
|
else
|
239
287
|
value
|
240
288
|
end
|
241
289
|
end
|
242
290
|
end
|
243
291
|
|
244
|
-
|
245
|
-
#=> #<
|
292
|
+
Document.new(path: '~/Documents/whatever.doc')
|
293
|
+
#=> #<Document path=#<Pathname:~/Documents/whatever.doc>>
|
246
294
|
|
247
|
-
|
248
|
-
#=> #<
|
295
|
+
Document.new(path: Pathname.new('~/Documents/whatever.doc'))
|
296
|
+
#=> #<Document path=#<Pathname:~/Documents/whatever.doc>>
|
249
297
|
|
250
|
-
|
251
|
-
#=>
|
252
|
-
#=>
|
298
|
+
Document.new(path: 42)
|
299
|
+
#=> ValueSemantics::InvalidValue:
|
300
|
+
#=> Attribute 'Document#path' is invalid: 42
|
253
301
|
```
|
254
302
|
|
255
303
|
You can also use any callable object as a coercer.
|
256
304
|
That means, you could use a lambda:
|
257
305
|
|
258
306
|
```ruby
|
259
|
-
class
|
307
|
+
class Document
|
260
308
|
include ValueSemantics.for_attributes {
|
261
|
-
|
309
|
+
path Pathname, coerce: ->(value) { Pathname.new(value) }
|
262
310
|
}
|
263
311
|
end
|
264
312
|
```
|
@@ -266,25 +314,25 @@ end
|
|
266
314
|
Or a custom class:
|
267
315
|
|
268
316
|
```ruby
|
269
|
-
class
|
317
|
+
class MyPathCoercer
|
270
318
|
def call(value)
|
271
|
-
|
319
|
+
Pathname.new(value)
|
272
320
|
end
|
273
321
|
end
|
274
322
|
|
275
|
-
class
|
323
|
+
class Document
|
276
324
|
include ValueSemantics.for_attributes {
|
277
|
-
|
325
|
+
path Pathname, coerce: MyPathCoercer.new
|
278
326
|
}
|
279
327
|
end
|
280
328
|
```
|
281
329
|
|
282
|
-
Or reuse an existing
|
330
|
+
Or reuse an existing method:
|
283
331
|
|
284
332
|
```ruby
|
285
|
-
class
|
333
|
+
class Document
|
286
334
|
include ValueSemantics.for_attributes {
|
287
|
-
|
335
|
+
path Pathname, coerce: Pathname.method(:new)
|
288
336
|
}
|
289
337
|
end
|
290
338
|
```
|
@@ -296,7 +344,22 @@ Another option is to raise an error within the coercion method.
|
|
296
344
|
|
297
345
|
Default attribute values also pass through coercion.
|
298
346
|
For example, the default value could be a string,
|
299
|
-
which would then be coerced into an `
|
347
|
+
which would then be coerced into an `Pathname` object.
|
348
|
+
|
349
|
+
|
350
|
+
## ValueSemantics::Struct
|
351
|
+
|
352
|
+
This is a convenience for making a new class and including ValueSemantics in
|
353
|
+
one step, similar to how `Struct` works from the Ruby standard library. For
|
354
|
+
example:
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
Cat = ValueSemantics::Struct.new do
|
358
|
+
name String, default: "Mittens"
|
359
|
+
end
|
360
|
+
|
361
|
+
Cat.new.name #=> "Mittens"
|
362
|
+
```
|
300
363
|
|
301
364
|
|
302
365
|
## Installation
|
@@ -321,7 +384,15 @@ Or install it yourself as:
|
|
321
384
|
Bug reports and pull requests are welcome on GitHub at:
|
322
385
|
https://github.com/tomdalling/value_semantics
|
323
386
|
|
324
|
-
Keep in mind that this gem aims to be as close to 100% backwards compatible as
|
387
|
+
Keep in mind that this gem aims to be as close to 100% backwards compatible as
|
388
|
+
possible.
|
389
|
+
|
390
|
+
I'm happy to accept PRs that:
|
391
|
+
|
392
|
+
- Improve error messages for a better developer experience, especially those
|
393
|
+
that support a TDD workflow.
|
394
|
+
- Add new, helpful validators
|
395
|
+
- Implement automatic freezing of value objects (must be opt-in)
|
325
396
|
|
326
397
|
## License
|
327
398
|
|
data/lib/value_semantics.rb
CHANGED
@@ -3,6 +3,7 @@ module ValueSemantics
|
|
3
3
|
class UnrecognizedAttributes < Error; end
|
4
4
|
class NoDefaultValue < Error; end
|
5
5
|
class MissingAttributes < Error; end
|
6
|
+
class InvalidValue < ArgumentError; end
|
6
7
|
|
7
8
|
NOT_SPECIFIED = Object.new.freeze
|
8
9
|
|
@@ -43,6 +44,39 @@ module ValueSemantics
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
47
|
+
#
|
48
|
+
# Makes the `.value_semantics` convenience method available to all classes
|
49
|
+
#
|
50
|
+
# `.value_semantics` is a shortcut for `include ValueSemantics.for_attributes`.
|
51
|
+
# Instead of:
|
52
|
+
#
|
53
|
+
# class Person
|
54
|
+
# include ValueSemantics.for_attributes {
|
55
|
+
# name String
|
56
|
+
# }
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# You can just write:
|
60
|
+
#
|
61
|
+
# class Person
|
62
|
+
# value_semantics do
|
63
|
+
# name String
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# Alternatively, you can `require 'value_semantics/monkey_patched'`, which
|
68
|
+
# will call this method automatically.
|
69
|
+
#
|
70
|
+
def self.monkey_patch!
|
71
|
+
Class.class_eval do
|
72
|
+
# @!visibility private
|
73
|
+
def value_semantics(&block)
|
74
|
+
include ValueSemantics.for_attributes(&block)
|
75
|
+
end
|
76
|
+
private :value_semantics
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
46
80
|
#
|
47
81
|
# All the class methods available on ValueSemantics classes
|
48
82
|
#
|
@@ -55,6 +89,11 @@ module ValueSemantics
|
|
55
89
|
# was included into this class.
|
56
90
|
#
|
57
91
|
def value_semantics
|
92
|
+
if block_given?
|
93
|
+
# caller is trying to use the monkey-patched Class method
|
94
|
+
raise "`#{self}` has already included ValueSemantics"
|
95
|
+
end
|
96
|
+
|
58
97
|
self::VALUE_SEMANTICS_RECIPE__
|
59
98
|
end
|
60
99
|
end
|
@@ -64,15 +103,31 @@ module ValueSemantics
|
|
64
103
|
#
|
65
104
|
module InstanceMethods
|
66
105
|
#
|
67
|
-
# Creates a value object based on a
|
106
|
+
# Creates a value object based on a hash of attributes
|
107
|
+
#
|
108
|
+
# @param attributes [#to_h] A hash of attribute values by name. Typically a
|
109
|
+
# `Hash`, but can be any object that responds to `#to_h`.
|
68
110
|
#
|
69
|
-
# @
|
70
|
-
#
|
71
|
-
# @raise [MissingAttributes] if given_attrs is missing any attributes that
|
72
|
-
#
|
111
|
+
# @raise [UnrecognizedAttributes] if given_attrs contains keys that are not
|
112
|
+
# attributes
|
113
|
+
# @raise [MissingAttributes] if given_attrs is missing any attributes that
|
114
|
+
# do not have defaults
|
115
|
+
# @raise [InvalidValue] if any attribute values do no pass their validators
|
116
|
+
# @raise [TypeError] if the argument does not respond to `#to_h`
|
73
117
|
#
|
74
|
-
def initialize(
|
75
|
-
|
118
|
+
def initialize(attributes = nil)
|
119
|
+
attributes_hash =
|
120
|
+
if attributes.respond_to?(:to_h)
|
121
|
+
attributes.to_h
|
122
|
+
else
|
123
|
+
raise TypeError, <<-END_MESSAGE.strip.gsub(/\s+/, ' ')
|
124
|
+
Can not initialize a `#{self.class}` with a `#{attributes.class}`
|
125
|
+
object. This argument is typically a `Hash` of attributes, but can
|
126
|
+
be any object that responds to `#to_h`.
|
127
|
+
END_MESSAGE
|
128
|
+
end
|
129
|
+
|
130
|
+
remaining_attrs = attributes_hash.dup
|
76
131
|
|
77
132
|
self.class.value_semantics.attributes.each do |attr|
|
78
133
|
key, value = attr.determine_from!(remaining_attrs, self.class)
|
@@ -81,8 +136,14 @@ module ValueSemantics
|
|
81
136
|
end
|
82
137
|
|
83
138
|
unless remaining_attrs.empty?
|
84
|
-
|
85
|
-
|
139
|
+
raise(
|
140
|
+
UnrecognizedAttributes,
|
141
|
+
"`#{self.class}` does not define attributes: " +
|
142
|
+
remaining_attrs
|
143
|
+
.keys
|
144
|
+
.map { |k| '`' + k.inspect + '`' }
|
145
|
+
.join(', ')
|
146
|
+
)
|
86
147
|
end
|
87
148
|
end
|
88
149
|
|
@@ -139,6 +200,20 @@ module ValueSemantics
|
|
139
200
|
|
140
201
|
"#<#{self.class} #{attrs}>"
|
141
202
|
end
|
203
|
+
|
204
|
+
def pretty_print(pp)
|
205
|
+
pp.object_group(self) do
|
206
|
+
to_h.each do |attr, value|
|
207
|
+
pp.breakable
|
208
|
+
pp.text("#{attr}=")
|
209
|
+
pp.pp(value)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def deconstruct_keys(_)
|
215
|
+
to_h
|
216
|
+
end
|
142
217
|
end
|
143
218
|
|
144
219
|
#
|
@@ -169,7 +244,7 @@ module ValueSemantics
|
|
169
244
|
coerce: nil)
|
170
245
|
generator = begin
|
171
246
|
if default_generator && !default.equal?(NOT_SPECIFIED)
|
172
|
-
raise ArgumentError, "Attribute
|
247
|
+
raise ArgumentError, "Attribute `#{name}` can not have both a `:default` and a `:default_generator`"
|
173
248
|
elsif default_generator
|
174
249
|
default_generator
|
175
250
|
elsif !default.equal?(NOT_SPECIFIED)
|
@@ -190,7 +265,7 @@ module ValueSemantics
|
|
190
265
|
def determine_from!(attr_hash, klass)
|
191
266
|
raw_value = attr_hash.fetch(name) do
|
192
267
|
if default_generator.equal?(NO_DEFAULT_GENERATOR)
|
193
|
-
raise MissingAttributes, "
|
268
|
+
raise MissingAttributes, "Attribute `#{klass}\##{name}` has no value"
|
194
269
|
else
|
195
270
|
default_generator.call
|
196
271
|
end
|
@@ -201,7 +276,7 @@ module ValueSemantics
|
|
201
276
|
if validate?(coerced_value)
|
202
277
|
[name, coerced_value]
|
203
278
|
else
|
204
|
-
raise
|
279
|
+
raise InvalidValue, "Attribute `#{klass}\##{name}` is invalid: #{coerced_value.inspect}"
|
205
280
|
end
|
206
281
|
end
|
207
282
|
|
@@ -284,13 +359,13 @@ module ValueSemantics
|
|
284
359
|
ArrayOf.new(element_validator)
|
285
360
|
end
|
286
361
|
|
287
|
-
def def_attr(*args)
|
288
|
-
__attributes << Attribute.define(*args)
|
362
|
+
def def_attr(*args, **kwargs)
|
363
|
+
__attributes << Attribute.define(*args, **kwargs)
|
289
364
|
end
|
290
365
|
|
291
|
-
def method_missing(name, *args)
|
366
|
+
def method_missing(name, *args, **kwargs)
|
292
367
|
if respond_to_missing?(name)
|
293
|
-
def_attr(name, *args)
|
368
|
+
def_attr(name, *args, **kwargs)
|
294
369
|
else
|
295
370
|
super
|
296
371
|
end
|
@@ -356,4 +431,22 @@ module ValueSemantics
|
|
356
431
|
end
|
357
432
|
end
|
358
433
|
|
434
|
+
#
|
435
|
+
# ValueSemantics equivalent of the Struct class from the Ruby standard
|
436
|
+
# library
|
437
|
+
#
|
438
|
+
class Struct
|
439
|
+
#
|
440
|
+
# Creates a new Class with ValueSemantics mixed in
|
441
|
+
#
|
442
|
+
# @yield a block containing ValueSemantics DSL
|
443
|
+
# @return [Class] the newly created class
|
444
|
+
#
|
445
|
+
def self.new(&block)
|
446
|
+
klass = Class.new
|
447
|
+
klass.include(ValueSemantics.for_attributes(&block))
|
448
|
+
klass
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
359
452
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: value_semantics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.15'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.15'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -30,14 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.7
|
33
|
+
version: '3.7'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.7
|
40
|
+
version: '3.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: super_diff
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: mutant-rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,22 +117,20 @@ executables: []
|
|
103
117
|
extensions: []
|
104
118
|
extra_rdoc_files: []
|
105
119
|
files:
|
106
|
-
-
|
107
|
-
- ".rspec"
|
108
|
-
- ".travis.yml"
|
109
|
-
- Gemfile
|
120
|
+
- CHANGELOG.md
|
110
121
|
- LICENSE.txt
|
111
122
|
- README.md
|
112
|
-
- bin/console
|
113
|
-
- bin/setup
|
114
|
-
- bin/test
|
115
123
|
- lib/value_semantics.rb
|
124
|
+
- lib/value_semantics/monkey_patched.rb
|
116
125
|
- lib/value_semantics/version.rb
|
117
|
-
- value_semantics.gemspec
|
118
126
|
homepage: https://github.com/tomdalling/value_semantics
|
119
127
|
licenses:
|
120
128
|
- MIT
|
121
|
-
metadata:
|
129
|
+
metadata:
|
130
|
+
bug_tracker_uri: https://github.com/tomdalling/value_semantics/issues
|
131
|
+
changelog_uri: https://github.com/tomdalling/value_semantics/blob/master/CHANGELOG.md
|
132
|
+
documentation_uri: https://github.com/tomdalling/value_semantics/blob/v3.4.0/README.md
|
133
|
+
source_code_uri: https://github.com/tomdalling/value_semantics
|
122
134
|
post_install_message:
|
123
135
|
rdoc_options: []
|
124
136
|
require_paths:
|
@@ -134,8 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
146
|
- !ruby/object:Gem::Version
|
135
147
|
version: '0'
|
136
148
|
requirements: []
|
137
|
-
|
138
|
-
rubygems_version: 2.7.7
|
149
|
+
rubygems_version: 3.0.8
|
139
150
|
signing_key:
|
140
151
|
specification_version: 4
|
141
152
|
summary: Makes value classes, with lightweight validation and coercion.
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
script: bin/test
|
3
|
-
|
4
|
-
# test old Ruby versions WITHOUT mutation testing
|
5
|
-
rvm:
|
6
|
-
- 2.3.8
|
7
|
-
- 2.4.5
|
8
|
-
- 2.5.3
|
9
|
-
env: MUTATION_TEST=false
|
10
|
-
|
11
|
-
# test the latest Ruby version WITH mutation testing
|
12
|
-
matrix:
|
13
|
-
include:
|
14
|
-
- rvm: 2.6.0
|
15
|
-
env: MUTATION_TEST=true
|
16
|
-
|
17
|
-
# deploy gem on tagged commits, on the latest Ruby version only
|
18
|
-
deploy:
|
19
|
-
provider: rubygems
|
20
|
-
on:
|
21
|
-
tags: true
|
22
|
-
env: MUTATION_TEST=true
|
23
|
-
api_key:
|
24
|
-
secure: nL74QuUczEpA0qbhSBN2zjGdviWgKB3wR6vFvwervv1MZNWmwOQUYe99Oq9kPeyc8/x2MR/H6PQm5qbrk/WAfRede01WxlZ/EBUW+9CYGrxcBsGONx9IULO8A0I8/yN/YJHW2vjo3dfR66EwVsXTVWq8U63PRRcwJIyTqnIiUm2sxauMQoPRBbXG+pD9v/EJSn3ugpdtxp0lVYDn8LDKk5Ho4/wbpY4ML11XUJa9mz9CyR/GsAzdy5FTXaDMOwuWOVEx9cab7m4qPOBhmlJY4TrmooFpxTxRwChcvByjq1IboEd2M3RT5on7Q/xDTlHSOuT0OS8mnS2AocGT4a1gC+W/xOlghgEcN+xs2V5mfucR6+iUYlCy32uz1w3ey7T2X5xN4ubut09r1xLi7eu1NisAoAc+GOJ4TIxQNqkeRhY4X/fs8j7SMfOEMDr6pPxSLKZxgSvExt+IbdcZD/uQ7rTBQkadYCbc9MX5dHazBievmar3ZsFffbIf+n13FVDXsaPgRt7DlFM5dqGrEwVwt1jFRhdFuDCjkj4QWOLn7E1uY3XqgrqGvgUBlF8Znwc6qicW8zxV4SIWhqIzCOH6L9WIZGLHNq0remoCd9sq9Ter9av3jL+6UmZRRAr+JceeZfZmsYIXKomECzleM9FXMx7FXlpjJKOlf3JnrfeCTwI=
|
data/Gemfile
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "value_semantics"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
data/bin/test
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
set -ue
|
3
|
-
|
4
|
-
MUTANT_PATTERN=${1:-ValueSemantics*}
|
5
|
-
|
6
|
-
# if $MUTATION_TEST is false, just run RSpec
|
7
|
-
if [[ "${MUTATION_TEST:-true}" == "false" ]] ; then
|
8
|
-
bundle exec rspec
|
9
|
-
else
|
10
|
-
bundle exec mutant \
|
11
|
-
--include lib \
|
12
|
-
--require value_semantics \
|
13
|
-
--use rspec "$MUTANT_PATTERN" \
|
14
|
-
# Mutant 0.8.24 introduces new mutations that cause infinite recursion inside
|
15
|
-
# #method_missing. These --ignore-subject lines prevent that from happening
|
16
|
-
#--ignore-subject "ValueSemantics::DSL#method_missing" \
|
17
|
-
#--ignore-subject "ValueSemantics::DSL#respond_to_missing?" \
|
18
|
-
#--ignore-subject "ValueSemantics::DSL#def_attr" \
|
19
|
-
fi
|
data/value_semantics.gemspec
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require "value_semantics/version"
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "value_semantics"
|
8
|
-
spec.version = ValueSemantics::VERSION
|
9
|
-
spec.authors = ["Tom Dalling"]
|
10
|
-
spec.email = [["tom", "@", "tomdalling.com"].join]
|
11
|
-
|
12
|
-
spec.summary = %q{Makes value classes, with lightweight validation and coercion.}
|
13
|
-
spec.description = %q{
|
14
|
-
Generates modules that provide conventional value semantics for a given set of attributes.
|
15
|
-
The behaviour is similar to an immutable `Struct` class,
|
16
|
-
plus extensible, lightweight validation and coercion.
|
17
|
-
}
|
18
|
-
spec.homepage = "https://github.com/tomdalling/value_semantics"
|
19
|
-
spec.license = "MIT"
|
20
|
-
|
21
|
-
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
|
-
f.match(%r{^(test|spec|features)/})
|
23
|
-
end
|
24
|
-
spec.bindir = "exe"
|
25
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = ["lib"]
|
27
|
-
|
28
|
-
spec.add_development_dependency "bundler", "~> 1.15"
|
29
|
-
spec.add_development_dependency "rspec", "~> 3.7.0"
|
30
|
-
spec.add_development_dependency "mutant-rspec"
|
31
|
-
spec.add_development_dependency "yard"
|
32
|
-
spec.add_development_dependency "byebug"
|
33
|
-
spec.add_development_dependency "gem-release"
|
34
|
-
end
|