lab42_data_class 0.1.0 → 0.3.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 +4 -4
- data/README.md +100 -11
- data/lib/lab42/data_class/proxy.rb +23 -4
- data/lib/lab42/data_class/version.rb +1 -1
- data/lib/lab42/data_class.rb +0 -1
- metadata +10 -7
- data/lib/lab42/data_class/for_module.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c062e2633e5881141e65113d2d60878733611bece697c933876ef1d9f62270e
|
4
|
+
data.tar.gz: bb5e524274338fda67063a8514cbe17a27c92ec8b62106061793456245729e30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddfaa83d7d62a4f3ccdb6df66cc1b3462ee626ac970b2654e138d338c3e0ba8e016882445552d8fff4ffb13636953f17757991625bc1c54b5b3883c1eed43f89
|
7
|
+
data.tar.gz: 4dae3c50dc6b01e5f311f61e4f8aaf2b712db66a4ace5172b91df242411246e71dc2b30f29cded81ab5b95c77c0858f81282e72a481aff0921d4dbeea990d2f6
|
data/README.md
CHANGED
@@ -6,7 +6,26 @@
|
|
6
6
|
|
7
7
|
# Lab42::DataClass
|
8
8
|
|
9
|
-
|
9
|
+
An immutable dataclass
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
```sh
|
14
|
+
gem install lab42_data_class
|
15
|
+
```
|
16
|
+
|
17
|
+
With bundler
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'lab42_data_class'
|
21
|
+
```
|
22
|
+
|
23
|
+
In your code
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'lab42/data_class'
|
27
|
+
```
|
28
|
+
|
10
29
|
|
11
30
|
## So what does it do?
|
12
31
|
|
@@ -41,7 +60,18 @@ And we can extract the values
|
|
41
60
|
expect(my_instance.to_h).to eq(name: "robert", email: nil)
|
42
61
|
```
|
43
62
|
|
44
|
-
#### Context: Immutable
|
63
|
+
#### Context: Immutable → self
|
64
|
+
|
65
|
+
Then `my_instance` is frozen:
|
66
|
+
```ruby
|
67
|
+
expect(my_instance).to be_frozen
|
68
|
+
```
|
69
|
+
And we cannot even mute `my_instance` by means of metaprogramming
|
70
|
+
```ruby
|
71
|
+
expect{ my_instance.instance_variable_set("@x", nil) }.to raise_error(FrozenError)
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Context: Immutable → Cloning
|
45
75
|
|
46
76
|
Given
|
47
77
|
```ruby
|
@@ -52,6 +82,10 @@ Then we have a new instance with the old instance unchanged
|
|
52
82
|
expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
|
53
83
|
expect(my_instance.to_h).to eq(name: "robert", email: nil)
|
54
84
|
```
|
85
|
+
And the new instance is frozen again
|
86
|
+
```ruby
|
87
|
+
expect(other_instance).to be_frozen
|
88
|
+
```
|
55
89
|
|
56
90
|
### Context: Defining behavior with blocks
|
57
91
|
|
@@ -71,26 +105,81 @@ Then I have defined a method on my dataclass
|
|
71
105
|
```ruby
|
72
106
|
expect(my_instance.show).to eq("<42>")
|
73
107
|
```
|
74
|
-
### Context: Making a dataclass from a class
|
75
108
|
|
76
|
-
|
109
|
+
### Context: Equality
|
110
|
+
|
111
|
+
Given two instances of a DataClass
|
112
|
+
```ruby
|
113
|
+
let(:data_class) { DataClass :a }
|
114
|
+
let(:instance1) { data_class.new(a: 1) }
|
115
|
+
let(:instance2) { data_class.new(a: 1) }
|
116
|
+
```
|
117
|
+
Then they are equal in the sense of `==` and `eql?`
|
118
|
+
```ruby
|
119
|
+
expect(instance1).to eq(instance2)
|
120
|
+
expect(instance2).to eq(instance1)
|
121
|
+
expect(instance1 == instance2).to be_truthy
|
122
|
+
expect(instance2 == instance1).to be_truthy
|
123
|
+
```
|
124
|
+
But not in the sense of `equal?`, of course
|
125
|
+
```ruby
|
126
|
+
expect(instance1).not_to be_equal(instance2)
|
127
|
+
expect(instance2).not_to be_equal(instance1)
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Context: Immutability of `dataclass` modified classes
|
131
|
+
|
132
|
+
Then we still get frozen instances
|
77
133
|
```ruby
|
78
|
-
|
79
|
-
|
80
|
-
|
134
|
+
expect(instance1).to be_frozen
|
135
|
+
```
|
136
|
+
|
137
|
+
### Context: Inheritance
|
138
|
+
|
139
|
+
... is a no, we do not want inheritance although we **like** code reuse, how to do it then?
|
140
|
+
|
141
|
+
Well there shall be many different possibilities, depending on your style, use case and
|
142
|
+
context, here is just one example:
|
143
|
+
|
144
|
+
Given a class factory
|
145
|
+
```ruby
|
146
|
+
let :token do
|
147
|
+
->(*a, **k) do
|
148
|
+
DataClass(*a, **(k.merge(text: "")))
|
149
|
+
end
|
81
150
|
end
|
82
151
|
```
|
83
152
|
|
84
|
-
Then we
|
153
|
+
Then we have reused the `token` successfully
|
85
154
|
```ruby
|
86
|
-
|
155
|
+
empty = token.()
|
156
|
+
integer = token.(:value)
|
157
|
+
boolean = token.(value: false)
|
158
|
+
|
159
|
+
expect(empty.new.to_h).to eq(text: "")
|
160
|
+
expect(integer.new(value: -1).to_h).to eq(text: "", value: -1)
|
161
|
+
expect(boolean.new.value).to eq(false)
|
87
162
|
```
|
88
163
|
|
89
|
-
|
164
|
+
#### Context: Mixing in a module can be used of course
|
165
|
+
|
166
|
+
Given a behavior like
|
90
167
|
```ruby
|
91
|
-
|
168
|
+
module Humanize
|
169
|
+
def humanize
|
170
|
+
"my value is #{value}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
let(:class_level) { DataClass(value: 1).include(Humanize) }
|
92
175
|
```
|
93
176
|
|
177
|
+
Then we can access the included method
|
178
|
+
```ruby
|
179
|
+
expect(class_level.new.humanize).to eq("my value is 1")
|
180
|
+
```
|
181
|
+
|
182
|
+
|
94
183
|
# LICENSE
|
95
184
|
|
96
185
|
Copyright 2022 Robert Dober robert.dober@gmail.com
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
3
4
|
module Lab42
|
4
5
|
module DataClass
|
5
6
|
class Proxy
|
6
|
-
attr_reader :actual_params, :block, :defaults, :klass, :
|
7
|
+
attr_reader :actual_params, :block, :defaults, :klass, :members, :positionals
|
7
8
|
|
8
9
|
def check!(**params)
|
9
10
|
@actual_params = params
|
@@ -30,8 +31,7 @@ module Lab42
|
|
30
31
|
|
31
32
|
private
|
32
33
|
def initialize(*args, **kwds, &blk)
|
33
|
-
@klass =
|
34
|
-
@has_parent = !!kwds.delete(:__klass__)
|
34
|
+
@klass = Class.new
|
35
35
|
|
36
36
|
@block = blk
|
37
37
|
@defaults = kwds
|
@@ -47,6 +47,23 @@ module Lab42
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def _define_eql?
|
51
|
+
->(*) do
|
52
|
+
define_method :== do |other|
|
53
|
+
other.is_a?(self.class) &&
|
54
|
+
to_h == other.to_h
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def _define_freezing_constructor
|
60
|
+
->(*) do
|
61
|
+
define_method :new do |*a, **p, &b|
|
62
|
+
super(*a, **p, &b).freeze
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
50
67
|
def _define_initializer
|
51
68
|
proxy = self
|
52
69
|
->(*) do
|
@@ -62,15 +79,17 @@ module Lab42
|
|
62
79
|
->(*) do
|
63
80
|
define_method :merge do |**params|
|
64
81
|
values = to_h.merge(params)
|
65
|
-
DataClass(*proxy.positionals,
|
82
|
+
DataClass(*proxy.positionals, **proxy.defaults, &proxy.block)
|
66
83
|
.new(**values)
|
67
84
|
end
|
68
85
|
end
|
69
86
|
end
|
70
87
|
|
71
88
|
def _define_methods
|
89
|
+
(class << klass; self end).module_eval(&_define_freezing_constructor)
|
72
90
|
klass.module_eval(&_define_to_h)
|
73
91
|
klass.module_eval(&_define_merge)
|
92
|
+
klass.module_eval(&_define_eql?)
|
74
93
|
klass.module_eval(&block) if block
|
75
94
|
end
|
76
95
|
|
data/lib/lab42/data_class.rb
CHANGED
metadata
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lab42_data_class
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-
|
11
|
+
date: 2022-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description: |
|
14
|
+
An Immutable DataClass for Ruby
|
15
|
+
|
16
|
+
Exposes a class factory function `Kernel::DataClass` and a class
|
17
|
+
modifer `Module#dataclass'
|
14
18
|
email: robert.dober@gmail.com
|
15
19
|
executables: []
|
16
20
|
extensions: []
|
@@ -19,14 +23,13 @@ files:
|
|
19
23
|
- LICENSE
|
20
24
|
- README.md
|
21
25
|
- lib/lab42/data_class.rb
|
22
|
-
- lib/lab42/data_class/for_module.rb
|
23
26
|
- lib/lab42/data_class/proxy.rb
|
24
27
|
- lib/lab42/data_class/version.rb
|
25
28
|
homepage: https://github.com/robertdober/lab42_data_class
|
26
29
|
licenses:
|
27
30
|
- Apache-2.0
|
28
31
|
metadata: {}
|
29
|
-
post_install_message:
|
32
|
+
post_install_message:
|
30
33
|
rdoc_options: []
|
31
34
|
require_paths:
|
32
35
|
- lib
|
@@ -42,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
45
|
version: '0'
|
43
46
|
requirements: []
|
44
47
|
rubygems_version: 3.3.3
|
45
|
-
signing_key:
|
48
|
+
signing_key:
|
46
49
|
specification_version: 4
|
47
50
|
summary: Finally a dataclass in ruby
|
48
51
|
test_files: []
|
@@ -1,15 +0,0 @@
|
|
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
|