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 +7 -0
- data/LICENSE +18 -0
- data/README.md +99 -0
- data/lib/lab42/data_class/for_module.rb +15 -0
- data/lib/lab42/data_class/proxy.rb +104 -0
- data/lib/lab42/data_class/version.rb +8 -0
- data/lib/lab42/data_class.rb +13 -0
- metadata +48 -0
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
|
+
[](https://rubygems.org/gems/lab42_data_class)
|
3
|
+
[](https://github.com/robertdober/lab42_data_class/actions)
|
4
|
+
[](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,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: []
|