lab42_data_class 0.4.1 → 0.6.0

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: 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: []