lab42_data_class 0.4.1 → 0.6.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: e64e5ffb63619de67de53f6618db6f25dc61e6a69b99fb6ae6cdc96446bbc490
4
- data.tar.gz: ccf55ee9ef5d9bd3beabba0f852910ab37294a23415df78ca800e5ae411905ab
3
+ metadata.gz: 1819694c4684f3f4e377f04f4eb17d0842a1075ff7859eeef443c66dd7cfb458
4
+ data.tar.gz: 5af74933329ed924d51a1820d666c671973004edbd3ab00f6ba93378df71180f
5
5
  SHA512:
6
- metadata.gz: c37f77d7fd444f7b64ba489e6d170b28e49ca176f74311f9aa0f269976e1561e56ba8561dd2f4b2f47cc526dc677943a98f9cd4c356c073bdc6bcebfcff29580
7
- data.tar.gz: ed8a76bd074ad26ac625b17aec759d36300d5f0ba4ebe6ed6c827ee38149d6b8303d5bca7e8c62575fad3f2c29513577c89de0c607d15d5e109a9e813de7e59c
6
+ metadata.gz: 5e14f2656f03703aa867dce065bf9e4770cf043fd97d035cab9a6b869d9fb6e9749c2de3eef0b948e02c7ade07f85f75423c4b2412ed154574a6672e28411d9f
7
+ data.tar.gz: f831c9c8094f7224de088f6f03847791bcc91a33db6e565fc1ef3f45d5f3f5e29d18b05dfbcdbc7dcc422755d0ab87dc73f0c50a9de45dcd3df0916d87fb1c16
data/README.md CHANGED
@@ -8,7 +8,12 @@
8
8
 
9
9
  # Lab42::DataClass
10
10
 
11
- An immutable Dataclass, Tuples and Triples
11
+
12
+ An Immutable DataClass for Ruby
13
+
14
+ Exposes a class factory function `Kernel::DataClass` and a class
15
+ modifer `Module#dataclass`, also creates two _tuple_ classes, `Pair` and
16
+ `Triple`
12
17
 
13
18
  ## Usage
14
19
 
@@ -35,6 +40,7 @@ Well let us [speculate about](https://github.com/RobertDober/speculate_about) it
35
40
 
36
41
  ## Context `DataClass`
37
42
 
43
+
38
44
  ### Context: `DataClass` function
39
45
 
40
46
  Given
@@ -280,6 +286,214 @@ Then we can pass it as keyword arguments
280
286
  expect(extract_value(**my_class.new)).to eq([1, base: 2])
281
287
  ```
282
288
 
289
+ ### Context: Constraints
290
+
291
+ Values of attributes of a `DataClass` can have constraints
292
+
293
+ Given a `DataClass` with constraints
294
+ ```ruby
295
+ let :switch do
296
+ DataClass(on: false).with_constraint(on: -> { [false, true].member? _1 })
297
+ end
298
+ ```
299
+
300
+ Then boolean values are acceptable
301
+ ```ruby
302
+ expect{ switch.new }.not_to raise_error
303
+ expect(switch.new.merge(on: true).on).to eq(true)
304
+ ```
305
+
306
+ But we can neither construct or merge with non boolean values
307
+ ```ruby
308
+ expect{ switch.new(on: nil) }
309
+ .to raise_error(Lab42::DataClass::ConstraintError, "value nil is not allowed for attribute :on")
310
+ expect{ switch.new.merge(on: 42) }
311
+ .to raise_error(Lab42::DataClass::ConstraintError, "value 42 is not allowed for attribute :on")
312
+ ```
313
+
314
+ And therefore defaultless attributes cannot have a constraint that is violated by a nil value
315
+ ```ruby
316
+ error_head = "constraint error during validation of default value of attribute :value"
317
+ error_body = " undefined method `>' for nil:NilClass"
318
+ error_message = [error_head, error_body].join("\n")
319
+
320
+ expect{ DataClass(value: nil).with_constraint(value: -> { _1 > 0 }) }
321
+ .to raise_error(Lab42::DataClass::ConstraintError, /#{error_message}/)
322
+ ```
323
+
324
+ And defining constraints for undefined attributes is not the best of ideas
325
+ ```ruby
326
+ expect { DataClass(a: 1).with_constraint(b: -> {true}) }
327
+ .to raise_error(ArgumentError, "constraints cannot be defined for undefined attributes [:b]")
328
+ ```
329
+
330
+
331
+ #### Context: Convenience Constraints
332
+
333
+ Often repeating patterns are implemented as non lambda constraints, depending on the type of a constraint
334
+ it is implicitly converted to a lambda as specified below:
335
+
336
+ Given a shortcut for our `ConstraintError`
337
+ ```ruby
338
+ let(:constraint_error) { Lab42::DataClass::ConstraintError }
339
+ let(:positive) { DataClass(:value) }
340
+ ```
341
+
342
+ ##### Symbols
343
+
344
+ ... are sent to the value of the attribute, this is not very surprising of course ;)
345
+
346
+ Then a first implementation of `Positive`
347
+ ```ruby
348
+ positive_by_symbol = positive.with_constraint(value: :positive?)
349
+
350
+ expect(positive_by_symbol.new(value: 1).value).to eq(1)
351
+ expect{positive_by_symbol.new(value: 0)}.to raise_error(constraint_error)
352
+ ```
353
+
354
+ ##### Arrays
355
+
356
+ ... are also sent to the value of the attribute, this time we can provide paramaters
357
+ And we can implement a different form of `Positive`
358
+ ```ruby
359
+ positive_by_ary = positive.with_constraint(value: [:>, 0])
360
+
361
+ expect(positive_by_ary.new(value: 1).value).to eq(1)
362
+ expect{positive_by_ary.new(value: 0)}.to raise_error(constraint_error)
363
+ ```
364
+
365
+ If however we are interested in membership we have to wrap the `Array` into a `Set`
366
+
367
+ ##### Membership
368
+
369
+ And this works with a `Set`
370
+ ```ruby
371
+ positive_by_set = positive.with_constraint(value: Set.new([*1..10]))
372
+
373
+ expect(positive_by_set.new(value: 1).value).to eq(1)
374
+ expect{positive_by_set.new(value: 0)}.to raise_error(constraint_error)
375
+ ```
376
+
377
+ And also with a `Range`
378
+ ```ruby
379
+ positive_by_range = positive.with_constraint(value: 1..Float::INFINITY)
380
+
381
+ expect(positive_by_range.new(value: 1).value).to eq(1)
382
+ expect{positive_by_range.new(value: 0)}.to raise_error(constraint_error)
383
+ ```
384
+
385
+ ##### Regexen
386
+
387
+ This seems quite obvious, and of course it works
388
+
389
+ Then we can also have a regex based constraint
390
+ ```ruby
391
+ vowel = DataClass(:word).with_constraint(word: /[aeiou]/)
392
+
393
+ expect(vowel.new(word: "alpha").word).to eq("alpha")
394
+ expect{vowel.new(word: "krk")}.to raise_error(constraint_error)
395
+ ```
396
+
397
+ ##### Other callable objects as constraints
398
+
399
+
400
+ Then we can also use instance methods to implement our `Positive`
401
+ ```ruby
402
+ positive_by_instance_method = positive.with_constraint(value: Fixnum.instance_method(:positive?))
403
+
404
+ expect(positive_by_instance_method.new(value: 1).value).to eq(1)
405
+ expect{positive_by_instance_method.new(value: 0)}.to raise_error(constraint_error)
406
+ ```
407
+
408
+ Or we can use methods to implement it
409
+ ```ruby
410
+ positive_by_method = positive.with_constraint(value: 0.method(:<))
411
+
412
+ expect(positive_by_method.new(value: 1).value).to eq(1)
413
+ expect{positive_by_method.new(value: 0)}.to raise_error(constraint_error)
414
+ ```
415
+
416
+ #### Context: Global Constraints aka __Validations__
417
+
418
+ So far we have only speculated about constraints concerning one attribute, however sometimes we want
419
+ to have arbitrary constraints which can only be calculated by access to more attributes
420
+
421
+ Given a `Point` DataClass
422
+ ```ruby
423
+ let(:point) { DataClass(:x, :y).validate{ |point| point.x > point.y } }
424
+ let(:validation_error) { Lab42::DataClass::ValidationError }
425
+ ```
426
+
427
+ Then we will get a `ValidationError` if we construct a point left of the main diagonal
428
+ ```ruby
429
+ expect{ point.new(x: 0, y: 1) }
430
+ .to raise_error(validation_error)
431
+ ```
432
+
433
+ But as validation might need more than the default values we will not execute them at compile time
434
+ ```ruby
435
+ expect{ DataClass(x: 0, y: 0).validate{ |inst| inst.x > inst.y } }
436
+ .to_not raise_error
437
+ ```
438
+
439
+ And we can name validations to get better error messages
440
+ ```ruby
441
+ better_point = DataClass(:x, :y).validate(:too_left){ |point| point.x > point.y }
442
+ ok_point = better_point.new(x: 1, y: 0)
443
+ expect{ ok_point.merge(y: 1) }
444
+ .to raise_error(validation_error, "too_left")
445
+ ```
446
+
447
+ And remark how bad unnamed validation errors might be
448
+ ```ruby
449
+ error_message_rgx = %r{
450
+ \#<Proc:0x[0-9a-f]+ \s .* spec/speculations/README_spec\.rb: \d+ > \z
451
+ }x
452
+ expect{ point.new(x: 0, y: 1) }
453
+ .to raise_error(validation_error, error_message_rgx)
454
+ ```
455
+
456
+ ### Context: Usage with `extend`
457
+
458
+ All the above mentioned features can be achieved with a more conventional syntax by extending a class
459
+ with `Lab42::DataClass`
460
+
461
+ Given a class that extends `DataClass`
462
+ ```ruby
463
+ let :my_class do
464
+ Class.new do
465
+ extend Lab42::DataClass
466
+ attributes :age, member: false
467
+ constraint :member, Set.new([false, true])
468
+ validate(:too_young_for_member) { |instance| !(instance.member && instance.age < 18) }
469
+ end
470
+ end
471
+ let(:constraint_error) { Lab42::DataClass::ConstraintError }
472
+ let(:validation_error) { Lab42::DataClass::ValidationError }
473
+ let(:my_instance) { my_class.new(age: 42) }
474
+ let(:my_vip) { my_instance.merge(member: true) }
475
+ ```
476
+
477
+ Then we can observe that instances of such a class
478
+ ```ruby
479
+ expect(my_instance.to_h).to eq(age: 42, member: false)
480
+ expect(my_vip.to_h).to eq(age: 42, member: true)
481
+ expect(my_instance.member).to be_falsy
482
+ ```
483
+
484
+ And we will get constraint errors if applicable
485
+ ```ruby
486
+ expect{my_instance.merge(member: nil)}
487
+ .to raise_error(constraint_error)
488
+ ```
489
+
490
+ And of course validations still work too
491
+ ```ruby
492
+ expect{ my_vip.merge(age: 17) }
493
+ .to raise_error(validation_error, "too_young_for_member")
494
+ ```
495
+
496
+
283
497
  ## Context: `Pair` and `Triple`
284
498
 
285
499
  Two special cases of a `DataClass` which behave like `Tuple` of size 2 and 3 in _Elixir_
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class ConstraintError < RuntimeError
6
+ end
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ def DataClass(*args, **kwds, &blk)
5
+ proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
6
+ proxy.define_class!
7
+ end
8
+
9
+ def Pair(first, second)
10
+ Lab42::Pair.new(first, second)
11
+ end
12
+
13
+ def Triple(first, second, third)
14
+ Lab42::Triple.new(first, second, third)
15
+ end
16
+ end
17
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ module Constraints
7
+ module Maker
8
+ extend self
9
+
10
+ def make_constraint(constraint)
11
+ case constraint
12
+ when Proc, Method
13
+ constraint
14
+ when Symbol
15
+ -> { _1.send(constraint) }
16
+ when Array
17
+ -> { _1.send(*constraint) }
18
+ when Regexp
19
+ -> { constraint.match?(_1) }
20
+ when UnboundMethod
21
+ -> { constraint.bind(_1).() }
22
+ else
23
+ _make_member_constraint(constraint)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def _make_member_constraint(constraint)
30
+ if constraint.respond_to?(:member?)
31
+ -> { constraint.member?(_1) }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constraints/maker"
4
+ module Lab42
5
+ module DataClass
6
+ class Proxy
7
+ module Constraints
8
+ def check_constraints_against_defaults(constraints)
9
+ errors = constraints
10
+ .map(&_check_constraint_against_default)
11
+ .compact
12
+ raise ConstraintError, errors.join("\n\n") unless errors.empty?
13
+ end
14
+
15
+ def define_constraint
16
+ ->((attr, constraint)) do
17
+ if members.member?(attr)
18
+ constraints[attr] << Maker.make_constraint(constraint)
19
+ nil
20
+ else
21
+ attr
22
+ end
23
+ end
24
+ end
25
+
26
+ def define_constraints(constraints)
27
+ errors = constraints.map(&define_constraint).compact
28
+ unless errors.empty?
29
+ raise ArgumentError,
30
+ "constraints cannot be defined for undefined attributes #{errors.inspect}"
31
+ end
32
+
33
+ check_constraints_against_defaults(constraints)
34
+ end
35
+
36
+ private
37
+
38
+ def _check_constraint_against_default
39
+ ->((attr, constraint)) do
40
+ if defaults.key?(attr)
41
+ _check_constraint_against_default_value(attr, defaults[attr], constraint)
42
+ end
43
+ end
44
+ end
45
+
46
+ def _check_constraint_against_default_value(attr, value, constraint)
47
+ unless Maker.make_constraint(constraint).(value)
48
+ "default value #{value.inspect} is not allowed for attribute #{attr.inspect}"
49
+ end
50
+ rescue StandardError => e
51
+ "constraint error during validation of default value of attribute #{attr.inspect}\n #{e.message}"
52
+ end
53
+
54
+ def _check_constraints_for_attr!
55
+ ->((k, v)) do
56
+ constraints[k]
57
+ .map(&_check_constraint!(k, v))
58
+ end
59
+ end
60
+
61
+ def _check_constraint!(attr, value)
62
+ ->(constraint) do
63
+ "value #{value.inspect} is not allowed for attribute #{attr.inspect}" unless constraint.(value)
64
+ rescue StandardError => e
65
+ "constraint error during validation of attribute #{attr.inspect}\n #{e.message}"
66
+ end
67
+ end
68
+
69
+ def _check_constraints!(params)
70
+ errors = params
71
+ .flat_map(&_check_constraints_for_attr!)
72
+ .compact
73
+
74
+ raise ConstraintError, errors.join("\n\n") unless errors.empty?
75
+ end
76
+
77
+ def _define_with_constraint
78
+ proxy = self
79
+ ->(*) do
80
+ define_method :with_constraint do |**constraints|
81
+ proxy.define_constraints(constraints)
82
+ self
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ module Memos
7
+ def constraints
8
+ @__constraints__ ||= Hash.new { |h, k| h[k] = [] }
9
+ end
10
+
11
+ def defaults
12
+ @__defaults__ ||= {}
13
+ end
14
+
15
+ def members
16
+ @__members__ ||= unless (positionals + defaults.keys).empty?
17
+ Set.new(positionals + defaults.keys)
18
+ end
19
+ end
20
+
21
+ def positionals
22
+ @__positionals__ ||= []
23
+ end
24
+
25
+ def validations
26
+ @__validations__ ||= []
27
+ end
28
+
29
+ private
30
+
31
+ def _missing_initializers
32
+ @___missing_initializers__ ||=
33
+ positionals - actual_params.keys
34
+ end
35
+
36
+ def _illegal_initializers
37
+ @___illegal_initializers__ ||=
38
+ actual_params.keys - positionals - defaults.keys
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ module Validations
7
+ def validate!(instance)
8
+ errors = validations
9
+ .map(&_check_validation!(instance))
10
+ .compact
11
+
12
+ raise ValidationError, errors.join("\n") unless errors.empty?
13
+ end
14
+
15
+ private
16
+
17
+ def _define_with_validations
18
+ proxy = self
19
+ ->(*) do
20
+ define_method :validate do |name = nil, &block|
21
+ proxy.validations << [name, block]
22
+ self
23
+ end
24
+ end
25
+ end
26
+
27
+ def _check_validation!(instance)
28
+ ->((name, validation)) do
29
+ unless validation.(instance)
30
+ name || validation
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ # SPDX-License-Identifier: Apache-2.0
@@ -1,16 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require_relative 'proxy/constraints'
5
+ require_relative 'proxy/memos'
6
+ require_relative 'proxy/validations'
4
7
  require_relative 'proxy/mixin'
5
8
  module Lab42
6
9
  module DataClass
7
10
  class Proxy
8
- attr_reader :actual_params, :block, :defaults, :klass, :members, :positionals
11
+ include Constraints, Memos, Validations
12
+
13
+ attr_reader :actual_params, :block, :klass
9
14
 
10
15
  def check!(**params)
11
16
  @actual_params = params
12
17
  raise ArgumentError, "missing initializers for #{_missing_initializers}" unless _missing_initializers.empty?
13
18
  raise ArgumentError, "illegal initializers #{_illegal_initializers}" unless _illegal_initializers.empty?
19
+
20
+ _check_constraints!(defaults.merge(params))
14
21
  end
15
22
 
16
23
  def define_class!
@@ -32,15 +39,22 @@ module Lab42
32
39
  .to_h
33
40
  end
34
41
 
42
+ def update!(with_positionals, with_keywords)
43
+ positionals.push(*with_positionals)
44
+ defaults.update(with_keywords)
45
+ end
46
+
35
47
  private
36
48
  def initialize(*args, **kwds, &blk)
37
- @klass = Class.new
49
+ @klass = if Class === args.first
50
+ args.shift
51
+ else
52
+ Class.new
53
+ end
38
54
 
39
55
  @block = blk
40
- @defaults = kwds
41
- @members = Set.new(args + kwds.keys)
42
- # TODO: Check for all symbols and no duplicates ⇒ v0.1.1
43
- @positionals = args
56
+ defaults.update(kwds)
57
+ positionals.push(*args)
44
58
  end
45
59
 
46
60
  def _define_attr_reader
@@ -64,6 +78,7 @@ module Lab42
64
78
  define_method :initialize do |**params|
65
79
  proxy.check!(**params)
66
80
  proxy.init(self, **params)
81
+ proxy.validate!(self)
67
82
  end
68
83
  end
69
84
  end
@@ -78,12 +93,19 @@ module Lab42
78
93
  end
79
94
 
80
95
  def _define_methods
81
- (class << klass; self end).module_eval(&_define_freezing_constructor)
82
- (class << klass; self end).module_eval(&_define_to_proc)
96
+ class << klass; self end
97
+ .tap { |singleton| _define_singleton_methods(singleton) }
83
98
  klass.module_eval(&_define_to_h)
84
99
  klass.module_eval(&_define_merge)
85
100
  end
86
101
 
102
+ def _define_singleton_methods(singleton)
103
+ singleton.module_eval(&_define_freezing_constructor)
104
+ singleton.module_eval(&_define_to_proc)
105
+ singleton.module_eval(&_define_with_constraint)
106
+ singleton.module_eval(&_define_with_validations)
107
+ end
108
+
87
109
  def _define_to_h
88
110
  proxy = self
89
111
  ->(*) do
@@ -107,16 +129,6 @@ module Lab42
107
129
  data_class_instance.instance_variable_set("@#{key}", value)
108
130
  end
109
131
  end
110
-
111
- def _missing_initializers
112
- @___missing_initializers__ ||=
113
- positionals - actual_params.keys
114
- end
115
-
116
- def _illegal_initializers
117
- @___illegal_initializers__ ||=
118
- actual_params.keys - positionals - defaults.keys
119
- end
120
132
  end
121
133
  end
122
134
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class ValidationError < RuntimeError
6
+ end
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.4.1"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -1,22 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './data_class/constraint_error'
4
+ require_relative './data_class/kernel'
5
+ require_relative './data_class/validation_error'
3
6
  require_relative './data_class/proxy'
4
7
  require_relative './pair'
5
8
  require_relative './triple'
6
9
 
7
- module Kernel
8
- def DataClass(*args, **kwds, &blk)
9
- proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
10
- proxy.define_class!
11
- end
10
+ module Lab42
11
+ module DataClass
12
+ def self.extended(extender)
13
+ proxy = Proxy.new(extender)
12
14
 
13
- def Pair(first, second)
14
- Lab42::Pair.new(first, second)
15
- end
15
+ extender.module_eval do
16
+ define_singleton_method(:__data_class_proxy__){ proxy }
17
+ end
18
+ end
19
+
20
+ def attributes(*args, **kwds)
21
+ __data_class_proxy__.tap do |proxy|
22
+ proxy.update!(args, kwds)
23
+ proxy.define_class!
24
+ end
25
+ end
16
26
 
17
- def Triple(first, second, third)
18
- Lab42::Triple.new(first, second, third)
27
+ def constraint(member, constraint = nil, &block)
28
+ raise ArgumentError, "must not provide constraint (2nd argument) and a block" if block && constraint
29
+
30
+ __data_class_proxy__.define_constraints(member => constraint || block)
31
+ end
19
32
  end
20
33
  end
21
-
22
34
  # SPDX-License-Identifier: Apache-2.0
metadata CHANGED
@@ -1,20 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_data_class
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  An Immutable DataClass for Ruby
15
15
 
16
16
  Exposes a class factory function `Kernel::DataClass` and a class
17
- modifer `Module#dataclass'
17
+ modifer `Module#dataclass', also creates two _tuple_ classes, `Pair` and
18
+ `Triple`
18
19
  email: robert.dober@gmail.com
19
20
  executables: []
20
21
  extensions: []
@@ -23,8 +24,15 @@ files:
23
24
  - LICENSE
24
25
  - README.md
25
26
  - lib/lab42/data_class.rb
27
+ - lib/lab42/data_class/constraint_error.rb
28
+ - lib/lab42/data_class/kernel.rb
26
29
  - lib/lab42/data_class/proxy.rb
30
+ - lib/lab42/data_class/proxy/constraints.rb
31
+ - lib/lab42/data_class/proxy/constraints/maker.rb
32
+ - lib/lab42/data_class/proxy/memos.rb
27
33
  - lib/lab42/data_class/proxy/mixin.rb
34
+ - lib/lab42/data_class/proxy/validations.rb
35
+ - lib/lab42/data_class/validation_error.rb
28
36
  - lib/lab42/data_class/version.rb
29
37
  - lib/lab42/eq_and_patterns.rb
30
38
  - lib/lab42/pair.rb
@@ -33,7 +41,7 @@ homepage: https://github.com/robertdober/lab42_data_class
33
41
  licenses:
34
42
  - Apache-2.0
35
43
  metadata: {}
36
- post_install_message:
44
+ post_install_message:
37
45
  rdoc_options: []
38
46
  require_paths:
39
47
  - lib
@@ -49,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
57
  version: '0'
50
58
  requirements: []
51
59
  rubygems_version: 3.3.3
52
- signing_key:
60
+ signing_key:
53
61
  specification_version: 4
54
62
  summary: Finally a dataclass in ruby
55
63
  test_files: []