lab42_data_class 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 03bada220d0a003c294685e37c3276b15786353f753b60cbafdaf6c78e3f986c
4
+ data.tar.gz: d36b51077068701de6ecd9db90831d6290b2620d4d403402403ad4af3f1420b2
5
+ SHA512:
6
+ metadata.gz: 931c7c48bd60f8cfdc8ae711b292facf18622de31fbf7d04116af68a70c559171384a024ef628195789a9c25121db5c10879ffc36d99d81fb71cbcd88e25baca
7
+ data.tar.gz: b2924660d7b0476092a9c85bd4d35c26fb59d4365d1a3f0ff2021343b336bdc28dd5a60dfd9fefb49a9f150cd0c955f71da0d896d72c17f37aa9a6f4352aff3f
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright © 2014 Dave Thomas, The Pragmatic Programmers
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ https://spdx.org/licenses/Apache-2.0.html
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+ Note:
16
+ Individual files contain the following tag instead of the full license text.
17
+
18
+ SPDX-License-Identifier: Apache-2.0
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+
2
+ [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
3
+ [![CI](https://github.com/robertdober/lab42_data_class/workflows/CI/badge.svg)](https://github.com/robertdober/lab42_data_class/actions)
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
+
6
+
7
+ # Lab42::DataClass
8
+
9
+ A dataclass with an immutable API (you can still change the state of the object with metaprogramming and `lab42_immutable` is not ready yet!)
10
+
11
+ ## So what does it do?
12
+
13
+ Well let us [speculate about](https://github.com/RobertDober/speculate_about) it to find out:
14
+
15
+ ### Context: `DataClass` function
16
+
17
+ Given
18
+ ```ruby
19
+ let(:my_data_class) { DataClass(:name, email: nil) }
20
+ let(:my_instance) { my_data_class.new(name: "robert") }
21
+ ```
22
+
23
+ Then we can access its fields
24
+ ```ruby
25
+ expect(my_instance.name).to eq("robert")
26
+ expect(my_instance.email).to be_nil
27
+ ```
28
+
29
+ But we cannot access undefined fields
30
+ ```ruby
31
+ expect{ my_instance.undefined }.to raise_error(NoMethodError)
32
+ ```
33
+
34
+ And we need to provide values to fields without defaults
35
+ ```ruby
36
+ expect{ my_data_class.new(email: "some@mail.org") }
37
+ .to raise_error(ArgumentError, "missing initializers for [:name]")
38
+ ```
39
+ And we can extract the values
40
+ ```ruby
41
+ expect(my_instance.to_h).to eq(name: "robert", email: nil)
42
+ ```
43
+
44
+ #### Context: Immutable
45
+
46
+ Given
47
+ ```ruby
48
+ let(:other_instance) { my_instance.merge(email: "robert@mail.provider") }
49
+ ```
50
+ Then we have a new instance with the old instance unchanged
51
+ ```ruby
52
+ expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
53
+ expect(my_instance.to_h).to eq(name: "robert", email: nil)
54
+ ```
55
+
56
+ ### Context: Defining behavior with blocks
57
+
58
+ Given
59
+ ```ruby
60
+ let :my_data_class do
61
+ DataClass :value, prefix: "<", suffix: ">" do
62
+ def show
63
+ [prefix, value, suffix].join
64
+ end
65
+ end
66
+ end
67
+ let(:my_instance) { my_data_class.new(value: 42) }
68
+ ```
69
+
70
+ Then I have defined a method on my dataclass
71
+ ```ruby
72
+ expect(my_instance.show).to eq("<42>")
73
+ ```
74
+ ### Context: Making a dataclass from a class
75
+
76
+ Given
77
+ ```ruby
78
+ class DC
79
+ dataclass x: 1, y: 41
80
+ def sum; x + y end
81
+ end
82
+ ```
83
+
84
+ Then we can define methods on it
85
+ ```ruby
86
+ expect(DC.new.sum).to eq(42)
87
+ ```
88
+
89
+ And we have a nice name for our instances
90
+ ```ruby
91
+ expect(DC.new.class.name).to eq("DC")
92
+ ```
93
+
94
+ # LICENSE
95
+
96
+ Copyright 2022 Robert Dober robert.dober@gmail.com
97
+
98
+ Apache-2.0 [c.f LICENSE](LICENSE)
99
+ <!-- SPDX-License-Identifier: Apache-2.0-->
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'proxy'
4
+
5
+ module Lab42
6
+ module DataClass
7
+ class ::Module
8
+ def dataclass(*args, **defaults)
9
+ proxy = Lab42::DataClass::Proxy.new(*args, __klass__: self, **defaults)
10
+ proxy.define_class!
11
+ end
12
+ end
13
+ end
14
+ end
15
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ attr_reader :actual_params, :block, :defaults, :klass, :has_parent, :members, :positionals
7
+
8
+ def check!(**params)
9
+ @actual_params = params
10
+ raise ArgumentError, "missing initializers for #{_missing_initializers}" unless _missing_initializers.empty?
11
+ raise ArgumentError, "illegal initializers #{_illegal_initializers}" unless _illegal_initializers.empty?
12
+ end
13
+
14
+ def define_class!
15
+ klass.module_eval(&_define_attr_reader)
16
+ klass.module_eval(&_define_initializer)
17
+ _define_methods
18
+ klass
19
+ end
20
+
21
+ def init(data_class, **params)
22
+ _init(data_class, defaults.merge(params))
23
+ end
24
+
25
+ def to_hash(data_class_instance)
26
+ members
27
+ .map { [_1, data_class_instance.instance_variable_get("@#{_1}")] }
28
+ .to_h
29
+ end
30
+
31
+ private
32
+ def initialize(*args, **kwds, &blk)
33
+ @klass = kwds.fetch(:__klass__){ Class.new }
34
+ @has_parent = !!kwds.delete(:__klass__)
35
+
36
+ @block = blk
37
+ @defaults = kwds
38
+ @members = Set.new(args + kwds.keys)
39
+ # TODO: Check for all symbols and no duplicates ⇒ v0.1.1
40
+ @positionals = args
41
+ end
42
+
43
+ def _define_attr_reader
44
+ proxy = self
45
+ ->(*) do
46
+ attr_reader(*proxy.members)
47
+ end
48
+ end
49
+
50
+ def _define_initializer
51
+ proxy = self
52
+ ->(*) do
53
+ define_method :initialize do |**params|
54
+ proxy.check!(**params)
55
+ proxy.init(self, **params)
56
+ end
57
+ end
58
+ end
59
+
60
+ def _define_merge
61
+ proxy = self
62
+ ->(*) do
63
+ define_method :merge do |**params|
64
+ values = to_h.merge(params)
65
+ DataClass(*proxy.positionals, __klass__: self.class, **proxy.defaults, &proxy.block)
66
+ .new(**values)
67
+ end
68
+ end
69
+ end
70
+
71
+ def _define_methods
72
+ klass.module_eval(&_define_to_h)
73
+ klass.module_eval(&_define_merge)
74
+ klass.module_eval(&block) if block
75
+ end
76
+
77
+ def _define_to_h
78
+ proxy = self
79
+ ->(*) do
80
+ define_method :to_h do
81
+ proxy.to_hash(self)
82
+ end
83
+ end
84
+ end
85
+
86
+ def _init(data_class_instance, params)
87
+ params.each do |key, value|
88
+ data_class_instance.instance_variable_set("@#{key}", value)
89
+ end
90
+ end
91
+
92
+ def _missing_initializers
93
+ @___missing_initializers__ ||=
94
+ positionals - actual_params.keys
95
+ end
96
+
97
+ def _illegal_initializers
98
+ @___illegal_initializers__ ||=
99
+ actual_params.keys - positionals - defaults.keys
100
+ end
101
+ end
102
+ end
103
+ end
104
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './data_class/for_module'
4
+ require_relative './data_class/proxy'
5
+
6
+ module Kernel
7
+ def DataClass(*args, **kwds, &blk)
8
+ proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
9
+ proxy.define_class!
10
+ end
11
+ end
12
+
13
+ # SPDX-License-Identifier: Apache-2.0
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lab42_data_class
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: introduces a new Kernel function DataClass
14
+ email: robert.dober@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - lib/lab42/data_class.rb
22
+ - lib/lab42/data_class/for_module.rb
23
+ - lib/lab42/data_class/proxy.rb
24
+ - lib/lab42/data_class/version.rb
25
+ homepage: https://github.com/robertdober/lab42_data_class
26
+ licenses:
27
+ - Apache-2.0
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 3.1.0
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.3.3
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Finally a dataclass in ruby
48
+ test_files: []