lab42_data_class 0.5.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: 70b03224373a4bdfe219cfb5d71d735f4a5b4d02c57531ea0f80a1729e542ce5
4
- data.tar.gz: b04020b335831a2c8fb8070bb17056874bb9103c5dda7b0e313da93c8afd80a0
3
+ metadata.gz: 1819694c4684f3f4e377f04f4eb17d0842a1075ff7859eeef443c66dd7cfb458
4
+ data.tar.gz: 5af74933329ed924d51a1820d666c671973004edbd3ab00f6ba93378df71180f
5
5
  SHA512:
6
- metadata.gz: c360a0eb554b9356c92e0a395d09a2dd29dc76f64d2a805ef6ac8e1699a057bb01fc8a41be4bf2f34f3e3ec5d9eff336817541bc59d39f7d94c74131303e2f0d
7
- data.tar.gz: 63395d4ede760ce63e7d92753ef370b2da666bafec309dee43f8c8549e768cc653d1976742441b583fd09a1a85535cc783834998ce494b903f2a40d380073a46
6
+ metadata.gz: 5e14f2656f03703aa867dce065bf9e4770cf043fd97d035cab9a6b869d9fb6e9749c2de3eef0b948e02c7ade07f85f75423c4b2412ed154574a6672e28411d9f
7
+ data.tar.gz: f831c9c8094f7224de088f6f03847791bcc91a33db6e565fc1ef3f45d5f3f5e29d18b05dfbcdbc7dcc422755d0ab87dc73f0c50a9de45dcd3df0916d87fb1c16
data/README.md CHANGED
@@ -40,6 +40,7 @@ Well let us [speculate about](https://github.com/RobertDober/speculate_about) it
40
40
 
41
41
  ## Context `DataClass`
42
42
 
43
+
43
44
  ### Context: `DataClass` function
44
45
 
45
46
  Given
@@ -452,6 +453,47 @@ And remark how bad unnamed validation errors might be
452
453
  .to raise_error(validation_error, error_message_rgx)
453
454
  ```
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
+
455
497
  ## Context: `Pair` and `Triple`
456
498
 
457
499
  Two special cases of a `DataClass` which behave like `Tuple` of size 2 and 3 in _Elixir_
@@ -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
@@ -23,6 +23,16 @@ module Lab42
23
23
  end
24
24
  end
25
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
+
26
36
  private
27
37
 
28
38
  def _check_constraint_against_default
@@ -68,13 +78,7 @@ module Lab42
68
78
  proxy = self
69
79
  ->(*) do
70
80
  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)
81
+ proxy.define_constraints(constraints)
78
82
  self
79
83
  end
80
84
  end
@@ -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
@@ -2,22 +2,22 @@
2
2
 
3
3
  require 'set'
4
4
  require_relative 'proxy/constraints'
5
+ require_relative 'proxy/memos'
5
6
  require_relative 'proxy/validations'
6
7
  require_relative 'proxy/mixin'
7
8
  module Lab42
8
9
  module DataClass
9
10
  class Proxy
10
- include Constraints, Validations
11
+ include Constraints, Memos, Validations
11
12
 
12
- attr_reader :actual_params, :all_params, :block, :constraints, :defaults, :klass, :members, :positionals
13
+ attr_reader :actual_params, :block, :klass
13
14
 
14
15
  def check!(**params)
15
16
  @actual_params = params
16
- @all_params = defaults.merge(params)
17
17
  raise ArgumentError, "missing initializers for #{_missing_initializers}" unless _missing_initializers.empty?
18
18
  raise ArgumentError, "illegal initializers #{_illegal_initializers}" unless _illegal_initializers.empty?
19
19
 
20
- _check_constraints!(all_params)
20
+ _check_constraints!(defaults.merge(params))
21
21
  end
22
22
 
23
23
  def define_class!
@@ -39,21 +39,22 @@ module Lab42
39
39
  .to_h
40
40
  end
41
41
 
42
- def validations
43
- @__validations__ ||= []
42
+ def update!(with_positionals, with_keywords)
43
+ positionals.push(*with_positionals)
44
+ defaults.update(with_keywords)
44
45
  end
45
46
 
46
47
  private
47
48
  def initialize(*args, **kwds, &blk)
48
- @klass = Class.new
49
-
50
- @constraints = Hash.new { |h, k| h[k] = [] }
49
+ @klass = if Class === args.first
50
+ args.shift
51
+ else
52
+ Class.new
53
+ end
51
54
 
52
55
  @block = blk
53
- @defaults = kwds
54
- @members = Set.new(args + kwds.keys)
55
- # TODO: Check for all symbols and no duplicates ⇒ v0.5.1
56
- @positionals = args
56
+ defaults.update(kwds)
57
+ positionals.push(*args)
57
58
  end
58
59
 
59
60
  def _define_attr_reader
@@ -128,16 +129,6 @@ module Lab42
128
129
  data_class_instance.instance_variable_set("@#{key}", value)
129
130
  end
130
131
  end
131
-
132
- def _missing_initializers
133
- @___missing_initializers__ ||=
134
- positionals - actual_params.keys
135
- end
136
-
137
- def _illegal_initializers
138
- @___illegal_initializers__ ||=
139
- actual_params.keys - positionals - defaults.keys
140
- end
141
132
  end
142
133
  end
143
134
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.5.1"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -1,24 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './data_class/constraint_error'
4
+ require_relative './data_class/kernel'
4
5
  require_relative './data_class/validation_error'
5
6
  require_relative './data_class/proxy'
6
7
  require_relative './pair'
7
8
  require_relative './triple'
8
9
 
9
- module Kernel
10
- def DataClass(*args, **kwds, &blk)
11
- proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
12
- proxy.define_class!
13
- end
10
+ module Lab42
11
+ module DataClass
12
+ def self.extended(extender)
13
+ proxy = Proxy.new(extender)
14
14
 
15
- def Pair(first, second)
16
- Lab42::Pair.new(first, second)
17
- 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
18
26
 
19
- def Triple(first, second, third)
20
- 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
21
32
  end
22
33
  end
23
-
24
34
  # SPDX-License-Identifier: Apache-2.0
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_data_class
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.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
11
  date: 2022-02-24 00:00:00.000000000 Z
@@ -25,9 +25,11 @@ files:
25
25
  - README.md
26
26
  - lib/lab42/data_class.rb
27
27
  - lib/lab42/data_class/constraint_error.rb
28
+ - lib/lab42/data_class/kernel.rb
28
29
  - lib/lab42/data_class/proxy.rb
29
30
  - lib/lab42/data_class/proxy/constraints.rb
30
31
  - lib/lab42/data_class/proxy/constraints/maker.rb
32
+ - lib/lab42/data_class/proxy/memos.rb
31
33
  - lib/lab42/data_class/proxy/mixin.rb
32
34
  - lib/lab42/data_class/proxy/validations.rb
33
35
  - lib/lab42/data_class/validation_error.rb
@@ -39,7 +41,7 @@ homepage: https://github.com/robertdober/lab42_data_class
39
41
  licenses:
40
42
  - Apache-2.0
41
43
  metadata: {}
42
- post_install_message:
44
+ post_install_message:
43
45
  rdoc_options: []
44
46
  require_paths:
45
47
  - lib
@@ -55,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
57
  version: '0'
56
58
  requirements: []
57
59
  rubygems_version: 3.3.3
58
- signing_key:
60
+ signing_key:
59
61
  specification_version: 4
60
62
  summary: Finally a dataclass in ruby
61
63
  test_files: []