lab42_data_class 0.1.1 → 0.3.1
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 +112 -11
- data/lib/lab42/data_class/proxy/mixin.rb +19 -0
- data/lib/lab42/data_class/proxy.rb +15 -5
- data/lib/lab42/data_class/version.rb +1 -1
- data/lib/lab42/data_class.rb +0 -1
- metadata +8 -4
- 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: 8e4127a34f013386209c71666f9d77e1b5c8fd439324443580e00c7e9018fdb4
|
|
4
|
+
data.tar.gz: c61d6f1648c9c73cd89883a0c0e6ebd6ea813f51dcdb26f141a99a962e4db5fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74b643eb6d43c00aedede31195354412a0b7dcabc5f5da0d1c3f5dbfca4dc03abc1b13a68e10307813153b6e8fe0c502a075a9c4ef17c61f1163ec251e03b3f7
|
|
7
|
+
data.tar.gz: '007384216191284048c0fcec62adedb04c8f41a549b92d7cdefcbaa3fa2f4536cce737d4a21a6c8d3648fe62a539fd9f201cd036566b9ff3251ac2929856a6a7'
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# Lab42::DataClass
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
An immutable dataclass
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
@@ -60,7 +60,18 @@ And we can extract the values
|
|
|
60
60
|
expect(my_instance.to_h).to eq(name: "robert", email: nil)
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
#### 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
|
|
64
75
|
|
|
65
76
|
Given
|
|
66
77
|
```ruby
|
|
@@ -71,6 +82,10 @@ Then we have a new instance with the old instance unchanged
|
|
|
71
82
|
expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
|
|
72
83
|
expect(my_instance.to_h).to eq(name: "robert", email: nil)
|
|
73
84
|
```
|
|
85
|
+
And the new instance is frozen again
|
|
86
|
+
```ruby
|
|
87
|
+
expect(other_instance).to be_frozen
|
|
88
|
+
```
|
|
74
89
|
|
|
75
90
|
### Context: Defining behavior with blocks
|
|
76
91
|
|
|
@@ -90,24 +105,110 @@ Then I have defined a method on my dataclass
|
|
|
90
105
|
```ruby
|
|
91
106
|
expect(my_instance.show).to eq("<42>")
|
|
92
107
|
```
|
|
93
|
-
### Context: Making a dataclass from a class
|
|
94
108
|
|
|
95
|
-
|
|
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?`
|
|
96
118
|
```ruby
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
133
|
+
```ruby
|
|
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
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Then we have reused the `token` successfully
|
|
154
|
+
```ruby
|
|
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)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Context: Mixing in a module can be used of course
|
|
165
|
+
|
|
166
|
+
Given a behavior like
|
|
167
|
+
```ruby
|
|
168
|
+
module Humanize
|
|
169
|
+
def humanize
|
|
170
|
+
"my value is #{value}"
|
|
171
|
+
end
|
|
100
172
|
end
|
|
173
|
+
|
|
174
|
+
let(:class_level) { DataClass(value: 1).include(Humanize) }
|
|
175
|
+
```
|
|
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
|
+
### Context: Pattern Matching
|
|
183
|
+
|
|
184
|
+
An `DataClass` object behaves like the result of it's `to_h` in pattern matching
|
|
185
|
+
|
|
186
|
+
Given
|
|
187
|
+
```ruby
|
|
188
|
+
let(:numbers) { DataClass(:name, values: []) }
|
|
189
|
+
let(:odds) { numbers.new(name: "odds", values: (1..4).map{ _1 + _1 + 1}) }
|
|
190
|
+
let(:evens) { numbers.new(name: "evens", values: (1..4).map{ _1 + _1}) }
|
|
101
191
|
```
|
|
102
192
|
|
|
103
|
-
Then we can
|
|
193
|
+
Then we can match accordingly
|
|
104
194
|
```ruby
|
|
105
|
-
|
|
195
|
+
match = case odds
|
|
196
|
+
in {name: "odds", values: [1, *]}
|
|
197
|
+
:not_really
|
|
198
|
+
in {name: "evens"}
|
|
199
|
+
:still_naaah
|
|
200
|
+
in {name: "odds", values: [hd, *]}
|
|
201
|
+
hd
|
|
202
|
+
else
|
|
203
|
+
:strange
|
|
204
|
+
end
|
|
205
|
+
expect(match).to eq(3)
|
|
106
206
|
```
|
|
107
207
|
|
|
108
|
-
And
|
|
208
|
+
And in `in` expressions
|
|
109
209
|
```ruby
|
|
110
|
-
|
|
210
|
+
evens => {values: [_, second, *]}
|
|
211
|
+
expect(second).to eq(4)
|
|
111
212
|
```
|
|
112
213
|
|
|
113
214
|
# LICENSE
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lab42
|
|
4
|
+
module DataClass
|
|
5
|
+
class Proxy
|
|
6
|
+
module Mixin
|
|
7
|
+
def ==(other)
|
|
8
|
+
other.is_a?(self.class) &&
|
|
9
|
+
to_h == other.to_h
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def deconstruct_keys(*)
|
|
13
|
+
to_h
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'set'
|
|
4
|
+
require_relative 'proxy/mixin'
|
|
4
5
|
module Lab42
|
|
5
6
|
module DataClass
|
|
6
7
|
class Proxy
|
|
7
|
-
attr_reader :actual_params, :block, :defaults, :klass, :
|
|
8
|
+
attr_reader :actual_params, :block, :defaults, :klass, :members, :positionals
|
|
8
9
|
|
|
9
10
|
def check!(**params)
|
|
10
11
|
@actual_params = params
|
|
@@ -16,6 +17,8 @@ module Lab42
|
|
|
16
17
|
klass.module_eval(&_define_attr_reader)
|
|
17
18
|
klass.module_eval(&_define_initializer)
|
|
18
19
|
_define_methods
|
|
20
|
+
klass.include(Mixin)
|
|
21
|
+
klass.module_eval(&block) if block
|
|
19
22
|
klass
|
|
20
23
|
end
|
|
21
24
|
|
|
@@ -31,8 +34,7 @@ module Lab42
|
|
|
31
34
|
|
|
32
35
|
private
|
|
33
36
|
def initialize(*args, **kwds, &blk)
|
|
34
|
-
@klass =
|
|
35
|
-
@has_parent = !!kwds.delete(:__klass__)
|
|
37
|
+
@klass = Class.new
|
|
36
38
|
|
|
37
39
|
@block = blk
|
|
38
40
|
@defaults = kwds
|
|
@@ -48,6 +50,14 @@ module Lab42
|
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
|
53
|
+
def _define_freezing_constructor
|
|
54
|
+
->(*) do
|
|
55
|
+
define_method :new do |*a, **p, &b|
|
|
56
|
+
super(*a, **p, &b).freeze
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
51
61
|
def _define_initializer
|
|
52
62
|
proxy = self
|
|
53
63
|
->(*) do
|
|
@@ -63,16 +73,16 @@ module Lab42
|
|
|
63
73
|
->(*) do
|
|
64
74
|
define_method :merge do |**params|
|
|
65
75
|
values = to_h.merge(params)
|
|
66
|
-
DataClass(*proxy.positionals,
|
|
76
|
+
DataClass(*proxy.positionals, **proxy.defaults, &proxy.block)
|
|
67
77
|
.new(**values)
|
|
68
78
|
end
|
|
69
79
|
end
|
|
70
80
|
end
|
|
71
81
|
|
|
72
82
|
def _define_methods
|
|
83
|
+
(class << klass; self end).module_eval(&_define_freezing_constructor)
|
|
73
84
|
klass.module_eval(&_define_to_h)
|
|
74
85
|
klass.module_eval(&_define_merge)
|
|
75
|
-
klass.module_eval(&block) if block
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
def _define_to_h
|
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Dober
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-01-
|
|
11
|
+
date: 2022-01-23 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,8 +23,8 @@ 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
|
|
27
|
+
- lib/lab42/data_class/proxy/mixin.rb
|
|
24
28
|
- lib/lab42/data_class/version.rb
|
|
25
29
|
homepage: https://github.com/robertdober/lab42_data_class
|
|
26
30
|
licenses:
|
|
@@ -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
|