lab42_data_class 0.3.3 → 0.5.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: 8b6b4667c08da4511e2e4889bf35d21badc1018d7c8ce61efb66c65162c1ca0f
4
- data.tar.gz: 0206dff912a52398e24488fd53aaac9c65ceceb3706b38bb562aac37d6d9d296
3
+ metadata.gz: 4d8b97ca60e61ad4b3ec85555649e5538ca35ef1018bcdf2a1cd9f86d4183718
4
+ data.tar.gz: 7076ca2be4a3a8e5428b2fa0fa14523c416d491d14a8ba254842405332215dba
5
5
  SHA512:
6
- metadata.gz: 23d410a7701bbdf4d175ab650e62fea5845c91d825d8346fa31955007571e2b862deccf478a831dab3e4404ef1d7f9f96018e13c672fe74a9592a534aceea186
7
- data.tar.gz: 1e349c67fa94a4633ad5e933e755a5ab762e3b13fa28cd5a7cdfdc18a70cfe7f5190dd9d2d0ac78474a44df2dcf4010785b155b8920706cd17da98125aa55ba7
6
+ metadata.gz: c74e746f2f71ac1cbd89a10566b5655e0cb4e2e9d6ff6031e147bb833b8af91b5e1251e89b0ed826401a1915e3654b8543d065138fc705491bf1c684d85d4b97
7
+ data.tar.gz: bf15dd5127834637d6c9e0e78cbaf4cb50e75afd03b3ba4d763e186ad65f00df9076892bd151bebad30d4e1d8d49b287225e00ef0ccf6eed3e7965ba1f47af62
data/README.md CHANGED
@@ -1,12 +1,19 @@
1
1
 
2
- [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
2
+ [![Issue Count](https://codeclimate.com/github/RobertDober/lab42_data_class/badges/issue_count.svg)](https://codeclimate.com/github/RobertDober/lab42_data_class)
3
3
  [![CI](https://github.com/robertdober/lab42_data_class/workflows/CI/badge.svg)](https://github.com/robertdober/lab42_data_class/actions)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/RobertDober/lab42_data_class/badge.svg?branch=main)](https://coveralls.io/github/RobertDober/lab42_data_class?branch=main)
5
+ [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
6
+ [![Gem Downloads](https://img.shields.io/gem/dt/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
5
7
 
6
8
 
7
9
  # Lab42::DataClass
8
10
 
9
- An immutable dataclass
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`
10
17
 
11
18
  ## Usage
12
19
 
@@ -31,6 +38,8 @@ require 'lab42/data_class'
31
38
 
32
39
  Well let us [speculate about](https://github.com/RobertDober/speculate_about) it to find out:
33
40
 
41
+ ## Context `DataClass`
42
+
34
43
  ### Context: `DataClass` function
35
44
 
36
45
  Given
@@ -276,6 +285,229 @@ Then we can pass it as keyword arguments
276
285
  expect(extract_value(**my_class.new)).to eq([1, base: 2])
277
286
  ```
278
287
 
288
+ ### Context: Constraints
289
+
290
+ Values of attributes of a `DataClass` can have constraints
291
+
292
+ Given a `DataClass` with constraints
293
+ ```ruby
294
+ let :switch do
295
+ DataClass(on: false).with_constraint(on: -> { [false, true].member? _1 })
296
+ end
297
+ ```
298
+
299
+ Then boolean values are acceptable
300
+ ```ruby
301
+ expect{ switch.new }.not_to raise_error
302
+ expect(switch.new.merge(on: true).on).to eq(true)
303
+ ```
304
+
305
+ But we can neither construct or merge with non boolean values
306
+ ```ruby
307
+ expect{ switch.new(on: nil) }
308
+ .to raise_error(Lab42::DataClass::ConstraintError, "value nil is not allowed for attribute :on")
309
+ expect{ switch.new.merge(on: 42) }
310
+ .to raise_error(Lab42::DataClass::ConstraintError, "value 42 is not allowed for attribute :on")
311
+ ```
312
+
313
+ And therefore defaultless attributes cannot have a constraint that is violated by a nil value
314
+ ```ruby
315
+ error_head = "constraint error during validation of default value of attribute :value"
316
+ error_body = " undefined method `>' for nil:NilClass"
317
+ error_message = [error_head, error_body].join("\n")
318
+
319
+ expect{ DataClass(value: nil).with_constraint(value: -> { _1 > 0 }) }
320
+ .to raise_error(Lab42::DataClass::ConstraintError, /#{error_message}/)
321
+ ```
322
+
323
+ And defining constraints for undefined attributes is not the best of ideas
324
+ ```ruby
325
+ expect { DataClass(a: 1).with_constraint(b: -> {true}) }
326
+ .to raise_error(ArgumentError, "constraints cannot be defined for undefined attributes [:b]")
327
+ ```
328
+
329
+
330
+ #### Context: Convenience Constraints
331
+
332
+ Often repeating patterns are implemented as non lambda constraints, depending on the type of a constraint
333
+ it is implicitly converted to a lambda as specified below:
334
+
335
+ Given a shortcut for our `ConstraintError`
336
+ ```ruby
337
+ let(:constraint_error) { Lab42::DataClass::ConstraintError }
338
+ let(:positive) { DataClass(:value) }
339
+ ```
340
+
341
+ ##### Symbols
342
+
343
+ ... are sent to the value of the attribute, this is not very surprising of course ;)
344
+
345
+ Then a first implementation of `Positive`
346
+ ```ruby
347
+ positive_by_symbol = positive.with_constraint(value: :positive?)
348
+
349
+ expect(positive_by_symbol.new(value: 1).value).to eq(1)
350
+ expect{positive_by_symbol.new(value: 0)}.to raise_error(constraint_error)
351
+ ```
352
+
353
+ ##### Arrays
354
+
355
+ ... are also sent to the value of the attribute, this time we can provide paramaters
356
+ And we can implement a different form of `Positive`
357
+ ```ruby
358
+ positive_by_ary = positive.with_constraint(value: [:>, 0])
359
+
360
+ expect(positive_by_ary.new(value: 1).value).to eq(1)
361
+ expect{positive_by_ary.new(value: 0)}.to raise_error(constraint_error)
362
+ ```
363
+
364
+ If however we are interested in membership we have to wrap the `Array` into a `Set`
365
+
366
+ ##### Membership
367
+
368
+ And this works with a `Set`
369
+ ```ruby
370
+ positive_by_set = positive.with_constraint(value: Set.new([*1..10]))
371
+
372
+ expect(positive_by_set.new(value: 1).value).to eq(1)
373
+ expect{positive_by_set.new(value: 0)}.to raise_error(constraint_error)
374
+ ```
375
+
376
+ And also with a `Range`
377
+ ```ruby
378
+ positive_by_range = positive.with_constraint(value: 1..Float::INFINITY)
379
+
380
+ expect(positive_by_range.new(value: 1).value).to eq(1)
381
+ expect{positive_by_range.new(value: 0)}.to raise_error(constraint_error)
382
+ ```
383
+
384
+ ##### Regexen
385
+
386
+ This seems quite obvious, and of course it works
387
+
388
+ Then we can also have a regex based constraint
389
+ ```ruby
390
+ vowel = DataClass(:word).with_constraint(word: /[aeiou]/)
391
+
392
+ expect(vowel.new(word: "alpha").word).to eq("alpha")
393
+ expect{vowel.new(word: "krk")}.to raise_error(constraint_error)
394
+ ```
395
+
396
+ ##### Other callable objects as constraints
397
+
398
+
399
+ Then we can also use instance methods to implement our `Positive`
400
+ ```ruby
401
+ positive_by_instance_method = positive.with_constraint(value: Fixnum.instance_method(:positive?))
402
+
403
+ expect(positive_by_instance_method.new(value: 1).value).to eq(1)
404
+ expect{positive_by_instance_method.new(value: 0)}.to raise_error(constraint_error)
405
+ ```
406
+
407
+ Or we can use methods to implement it
408
+ ```ruby
409
+ positive_by_method = positive.with_constraint(value: 0.method(:<))
410
+
411
+ expect(positive_by_method.new(value: 1).value).to eq(1)
412
+ expect{positive_by_method.new(value: 0)}.to raise_error(constraint_error)
413
+ ```
414
+
415
+ #### Context: Global Constraints aka __Validations__
416
+
417
+ So far we have only speculated about constraints concerning one attribute, however sometimes we want
418
+ to have arbitrary constraints which can only be calculated by access to more attributes
419
+
420
+ Given a `Point` DataClass
421
+ ```ruby
422
+ let(:point) { DataClass(:x, :y).validate{ |point| point.x > point.y } }
423
+ let(:validation_error) { Lab42::DataClass::ValidationError }
424
+ ```
425
+
426
+ Then we will get a `ValidationError` if we construct a point left of the main diagonal
427
+ ```ruby
428
+ expect{ point.new(x: 0, y: 1) }
429
+ .to raise_error(validation_error)
430
+ ```
431
+
432
+ But as validation might need more than the default values we will not execute them at compile time
433
+ ```ruby
434
+ expect{ DataClass(x: 0, y: 0).validate{ |inst| inst.x > inst.y } }
435
+ .to_not raise_error
436
+ ```
437
+
438
+ And we can name validations to get better error messages
439
+ ```ruby
440
+ better_point = DataClass(:x, :y).validate(:too_left){ |point| point.x > point.y }
441
+ ok_point = better_point.new(x: 1, y: 0)
442
+ expect{ ok_point.merge(y: 1) }
443
+ .to raise_error(validation_error, "too_left")
444
+ ```
445
+
446
+ And remark how bad unnamed validation errors might be
447
+ ```ruby
448
+ error_message_rgx = %r{
449
+ \#<Proc:0x[0-9a-f]+ \s .* spec/speculations/README_spec\.rb: \d+ > \z
450
+ }x
451
+ expect{ point.new(x: 0, y: 1) }
452
+ .to raise_error(validation_error, error_message_rgx)
453
+ ```
454
+
455
+ ## Context: `Pair` and `Triple`
456
+
457
+ Two special cases of a `DataClass` which behave like `Tuple` of size 2 and 3 in _Elixir_
458
+
459
+
460
+ They distinguish themselves from `DataClass` classes by accepting only positional arguments, and
461
+ cannot be converted to hashes.
462
+
463
+ These are actually two classes and not class factories as they have a fixed interface , but let us speculate about them to learn what they can do for us.
464
+
465
+ ### Context: Constructor functions
466
+
467
+ Given a pair
468
+ ```ruby
469
+ let(:token) { Pair("12", 12) }
470
+ let(:node) { Triple("42", 4, 2) }
471
+ ```
472
+
473
+ Then we can access their elements
474
+ ```ruby
475
+ expect(token.first).to eq("12")
476
+ expect(token.second).to eq(12)
477
+ expect(node.first).to eq("42")
478
+ expect(node.second).to eq(4)
479
+ expect(node.third).to eq(2)
480
+ ```
481
+
482
+ And we can treat them like _Indexable_
483
+ ```ruby
484
+ expect(token[1]).to eq(12)
485
+ expect(token[-2]).to eq("12")
486
+ expect(node[2]).to eq(2)
487
+ ```
488
+
489
+ And convert them to arrays of course
490
+ ```ruby
491
+ expect(token.to_a).to eq(["12", 12])
492
+ expect(node.to_a).to eq(["42", 4, 2])
493
+ ```
494
+
495
+ And they behave like arrays in pattern matching too
496
+ ```ruby
497
+ token => [str, int]
498
+ node => [root, lft, rgt]
499
+ expect(str).to eq("12")
500
+ expect(int).to eq(12)
501
+ expect(root).to eq("42")
502
+ expect(lft).to eq(4)
503
+ expect(rgt).to eq(2)
504
+ ```
505
+
506
+ And of course the factory functions are equivalent to the constructors
507
+ ```ruby
508
+ expect(token).to eq(Lab42::Pair.new("12", 12))
509
+ expect(node).to eq(Lab42::Triple.new("42", 4, 2))
510
+ ```
279
511
 
280
512
  # LICENSE
281
513
 
@@ -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,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,86 @@
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
+ private
27
+
28
+ def _check_constraint_against_default
29
+ ->((attr, constraint)) do
30
+ if defaults.key?(attr)
31
+ _check_constraint_against_default_value(attr, defaults[attr], constraint)
32
+ end
33
+ end
34
+ end
35
+
36
+ def _check_constraint_against_default_value(attr, value, constraint)
37
+ unless constraint.(value)
38
+ "default value #{value.inspect} is not allowed for attribute #{attr.inspect}"
39
+ end
40
+ rescue StandardError => e
41
+ "constraint error during validation of default value of attribute #{attr.inspect}\n #{e.message}"
42
+ end
43
+
44
+ def _check_constraints_for_attr!
45
+ ->((k, v)) do
46
+ constraints[k]
47
+ .map(&_check_constraint!(k, v))
48
+ end
49
+ end
50
+
51
+ def _check_constraint!(attr, value)
52
+ ->(constraint) do
53
+ "value #{value.inspect} is not allowed for attribute #{attr.inspect}" unless constraint.(value)
54
+ rescue StandardError => e
55
+ "constraint error during validation of attribute #{attr.inspect}\n #{e.message}"
56
+ end
57
+ end
58
+
59
+ def _check_constraints!(params)
60
+ errors = params
61
+ .flat_map(&_check_constraints_for_attr!)
62
+ .compact
63
+
64
+ raise ConstraintError, errors.join("\n\n") unless errors.empty?
65
+ end
66
+
67
+ def _define_with_constraint
68
+ proxy = self
69
+ ->(*) do
70
+ define_method :with_constraint do |**constraints|
71
+ errors = constraints.map(&proxy.define_constraint).compact
72
+ unless errors.empty?
73
+ raise ArgumentError,
74
+ "constraints cannot be defined for undefined attributes #{errors.inspect}"
75
+ end
76
+
77
+ proxy.check_constraints_against_defaults(constraints)
78
+ self
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ # 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/validations'
4
6
  require_relative 'proxy/mixin'
5
7
  module Lab42
6
8
  module DataClass
7
9
  class Proxy
8
- attr_reader :actual_params, :block, :defaults, :klass, :members, :positionals
10
+ include Constraints, Validations
11
+
12
+ attr_reader :actual_params, :all_params, :block, :constraints, :defaults, :klass, :members, :positionals
9
13
 
10
14
  def check!(**params)
11
15
  @actual_params = params
16
+ @all_params = defaults.merge(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!(all_params)
14
21
  end
15
22
 
16
23
  def define_class!
@@ -32,14 +39,20 @@ module Lab42
32
39
  .to_h
33
40
  end
34
41
 
42
+ def validations
43
+ @__validations__ ||= []
44
+ end
45
+
35
46
  private
36
47
  def initialize(*args, **kwds, &blk)
37
48
  @klass = Class.new
38
49
 
50
+ @constraints = Hash.new { |h, k| h[k] = [] }
51
+
39
52
  @block = blk
40
53
  @defaults = kwds
41
54
  @members = Set.new(args + kwds.keys)
42
- # TODO: Check for all symbols and no duplicates ⇒ v0.1.1
55
+ # TODO: Check for all symbols and no duplicates ⇒ v0.5.1
43
56
  @positionals = args
44
57
  end
45
58
 
@@ -64,28 +77,34 @@ module Lab42
64
77
  define_method :initialize do |**params|
65
78
  proxy.check!(**params)
66
79
  proxy.init(self, **params)
80
+ proxy.validate!(self)
67
81
  end
68
82
  end
69
83
  end
70
84
 
71
85
  def _define_merge
72
- proxy = self
73
86
  ->(*) do
74
87
  define_method :merge do |**params|
75
88
  values = to_h.merge(params)
76
- DataClass(*proxy.positionals, **proxy.defaults, &proxy.block)
77
- .new(**values)
89
+ self.class.new(**values)
78
90
  end
79
91
  end
80
92
  end
81
93
 
82
94
  def _define_methods
83
- (class << klass; self end).module_eval(&_define_freezing_constructor)
84
- (class << klass; self end).module_eval(&_define_to_proc)
95
+ class << klass; self end
96
+ .tap { |singleton| _define_singleton_methods(singleton) }
85
97
  klass.module_eval(&_define_to_h)
86
98
  klass.module_eval(&_define_merge)
87
99
  end
88
100
 
101
+ def _define_singleton_methods(singleton)
102
+ singleton.module_eval(&_define_freezing_constructor)
103
+ singleton.module_eval(&_define_to_proc)
104
+ singleton.module_eval(&_define_with_constraint)
105
+ singleton.module_eval(&_define_with_validations)
106
+ end
107
+
89
108
  def _define_to_h
90
109
  proxy = self
91
110
  ->(*) do
@@ -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.3.3"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -1,12 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './data_class/constraint_error'
4
+ require_relative './data_class/validation_error'
3
5
  require_relative './data_class/proxy'
6
+ require_relative './pair'
7
+ require_relative './triple'
4
8
 
5
9
  module Kernel
6
10
  def DataClass(*args, **kwds, &blk)
7
11
  proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
8
12
  proxy.define_class!
9
13
  end
14
+
15
+ def Pair(first, second)
16
+ Lab42::Pair.new(first, second)
17
+ end
18
+
19
+ def Triple(first, second, third)
20
+ Lab42::Triple.new(first, second, third)
21
+ end
10
22
  end
11
23
 
12
24
  # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module EqAndPatterns
5
+ def [](idx)
6
+ to_a[idx]
7
+ end
8
+
9
+ def ==(other)
10
+ other.is_a?(self.class) &&
11
+ to_a == other.to_a
12
+ end
13
+
14
+ def deconstruct(*)
15
+ to_a
16
+ end
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
data/lib/lab42/pair.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eq_and_patterns'
4
+ module Lab42
5
+ class Pair
6
+ attr_reader :first, :second
7
+ include EqAndPatterns
8
+
9
+ def to_a
10
+ [first, second]
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(first, second)
16
+ @first = first
17
+ @second = second
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eq_and_patterns'
4
+ module Lab42
5
+ class Triple
6
+ attr_reader :first, :second, :third
7
+ include EqAndPatterns
8
+
9
+ def to_a
10
+ [first, second, third]
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(first, second, third)
16
+ @first = first
17
+ @second = second
18
+ @third = third
19
+ end
20
+ end
21
+ end
22
+ # 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.3.3
4
+ version: 0.5.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-01-31 00:00:00.000000000 Z
11
+ date: 2022-02-23 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,14 +24,22 @@ files:
23
24
  - LICENSE
24
25
  - README.md
25
26
  - lib/lab42/data_class.rb
27
+ - lib/lab42/data_class/constraint_error.rb
26
28
  - lib/lab42/data_class/proxy.rb
29
+ - lib/lab42/data_class/proxy/constraints.rb
30
+ - lib/lab42/data_class/proxy/constraints/maker.rb
27
31
  - lib/lab42/data_class/proxy/mixin.rb
32
+ - lib/lab42/data_class/proxy/validations.rb
33
+ - lib/lab42/data_class/validation_error.rb
28
34
  - lib/lab42/data_class/version.rb
35
+ - lib/lab42/eq_and_patterns.rb
36
+ - lib/lab42/pair.rb
37
+ - lib/lab42/triple.rb
29
38
  homepage: https://github.com/robertdober/lab42_data_class
30
39
  licenses:
31
40
  - Apache-2.0
32
41
  metadata: {}
33
- post_install_message:
42
+ post_install_message:
34
43
  rdoc_options: []
35
44
  require_paths:
36
45
  - lib
@@ -46,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
55
  version: '0'
47
56
  requirements: []
48
57
  rubygems_version: 3.3.3
49
- signing_key:
58
+ signing_key:
50
59
  specification_version: 4
51
60
  summary: Finally a dataclass in ruby
52
61
  test_files: []