lab42_data_class 0.3.3 → 0.5.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: 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: []