lab42_data_class 0.2.0 → 0.3.3
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 +145 -20
- data/lib/lab42/data_class/proxy/mixin.rb +19 -0
- data/lib/lab42/data_class/proxy.rb +16 -15
- data/lib/lab42/data_class/version.rb +1 -1
- data/lib/lab42/data_class.rb +0 -1
- metadata +3 -3
- 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: 8b6b4667c08da4511e2e4889bf35d21badc1018d7c8ce61efb66c65162c1ca0f
|
4
|
+
data.tar.gz: 0206dff912a52398e24488fd53aaac9c65ceceb3706b38bb562aac37d6d9d296
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23d410a7701bbdf4d175ab650e62fea5845c91d825d8346fa31955007571e2b862deccf478a831dab3e4404ef1d7f9f96018e13c672fe74a9592a534aceea186
|
7
|
+
data.tar.gz: 1e349c67fa94a4633ad5e933e755a5ab762e3b13fa28cd5a7cdfdc18a70cfe7f5190dd9d2d0ac78474a44df2dcf4010785b155b8920706cd17da98125aa55ba7
|
data/README.md
CHANGED
@@ -105,25 +105,6 @@ Then I have defined a method on my dataclass
|
|
105
105
|
```ruby
|
106
106
|
expect(my_instance.show).to eq("<42>")
|
107
107
|
```
|
108
|
-
### Context: Making a dataclass from a class
|
109
|
-
|
110
|
-
Given
|
111
|
-
```ruby
|
112
|
-
class DC
|
113
|
-
dataclass x: 1, y: 41
|
114
|
-
def sum; x + y end
|
115
|
-
end
|
116
|
-
```
|
117
|
-
|
118
|
-
Then we can define methods on it
|
119
|
-
```ruby
|
120
|
-
expect(DC.new.sum).to eq(42)
|
121
|
-
```
|
122
|
-
|
123
|
-
And we have a nice name for our instances
|
124
|
-
```ruby
|
125
|
-
expect(DC.new.class.name).to eq("DC")
|
126
|
-
```
|
127
108
|
|
128
109
|
### Context: Equality
|
129
110
|
|
@@ -146,12 +127,156 @@ But not in the sense of `equal?`, of course
|
|
146
127
|
expect(instance2).not_to be_equal(instance1)
|
147
128
|
```
|
148
129
|
|
149
|
-
#### Immutability of `dataclass` modified classes
|
130
|
+
#### Context: Immutability of `dataclass` modified classes
|
150
131
|
|
151
132
|
Then we still get frozen instances
|
152
133
|
```ruby
|
153
134
|
expect(instance1).to be_frozen
|
154
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
|
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
|
+
A `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}) }
|
191
|
+
```
|
192
|
+
|
193
|
+
Then we can match accordingly
|
194
|
+
```ruby
|
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)
|
206
|
+
```
|
207
|
+
|
208
|
+
And in `in` expressions
|
209
|
+
```ruby
|
210
|
+
evens => {values: [_, second, *]}
|
211
|
+
expect(second).to eq(4)
|
212
|
+
```
|
213
|
+
|
214
|
+
#### Context: In Case Statements
|
215
|
+
|
216
|
+
Given a nice little dataclass `Box`
|
217
|
+
```ruby
|
218
|
+
let(:box) { DataClass content: nil }
|
219
|
+
```
|
220
|
+
|
221
|
+
Then we can also use it in a case statement
|
222
|
+
```ruby
|
223
|
+
value = case box.new
|
224
|
+
when box
|
225
|
+
42
|
226
|
+
else
|
227
|
+
0
|
228
|
+
end
|
229
|
+
expect(value).to eq(42)
|
230
|
+
```
|
231
|
+
|
232
|
+
And all the associated methods
|
233
|
+
```ruby
|
234
|
+
expect(box.new).to be_a(box)
|
235
|
+
expect(box === box.new).to be_truthy
|
236
|
+
```
|
237
|
+
|
238
|
+
### Context: Behaving like a `Proc`
|
239
|
+
|
240
|
+
It is useful to be able to filter heterogeneous lists of `DataClass` instances by means of `&to_proc`, therefore
|
241
|
+
|
242
|
+
Given two different `DataClass` objects
|
243
|
+
```ruby
|
244
|
+
let(:class1) { DataClass :value }
|
245
|
+
let(:class2) { DataClass :value }
|
246
|
+
```
|
247
|
+
|
248
|
+
And a list of instances
|
249
|
+
```ruby
|
250
|
+
let(:list) {[class1.new(value: 1), class2.new(value: 2), class1.new(value: 3)]}
|
251
|
+
```
|
252
|
+
|
253
|
+
Then we can filter
|
254
|
+
```ruby
|
255
|
+
expect(list.filter(&class2)).to eq([class2.new(value: 2)])
|
256
|
+
```
|
257
|
+
|
258
|
+
### Context: Behaving like a `Hash`
|
259
|
+
|
260
|
+
We have already seen the `to_h` method, however if we want to pass an instance of `DataClass` as
|
261
|
+
keyword parameters we need an implementation of `to_hash`, which of course is just an alias
|
262
|
+
|
263
|
+
Given this keyword method
|
264
|
+
```ruby
|
265
|
+
def extract_value(value:, **others)
|
266
|
+
[value, others]
|
267
|
+
end
|
268
|
+
```
|
269
|
+
And this `DataClass`:
|
270
|
+
```ruby
|
271
|
+
let(:my_class) { DataClass(value: 1, base: 2) }
|
272
|
+
```
|
273
|
+
|
274
|
+
Then we can pass it as keyword arguments
|
275
|
+
```ruby
|
276
|
+
expect(extract_value(**my_class.new)).to eq([1, base: 2])
|
277
|
+
```
|
278
|
+
|
279
|
+
|
155
280
|
# LICENSE
|
156
281
|
|
157
282
|
Copyright 2022 Robert Dober robert.dober@gmail.com
|
@@ -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,15 +50,6 @@ module Lab42
|
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
def _define_eql?
|
52
|
-
->(*) do
|
53
|
-
define_method :== do |other|
|
54
|
-
other.is_a?(self.class) &&
|
55
|
-
to_h == other.to_h
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
53
|
def _define_freezing_constructor
|
61
54
|
->(*) do
|
62
55
|
define_method :new do |*a, **p, &b|
|
@@ -80,7 +73,7 @@ module Lab42
|
|
80
73
|
->(*) do
|
81
74
|
define_method :merge do |**params|
|
82
75
|
values = to_h.merge(params)
|
83
|
-
DataClass(*proxy.positionals,
|
76
|
+
DataClass(*proxy.positionals, **proxy.defaults, &proxy.block)
|
84
77
|
.new(**values)
|
85
78
|
end
|
86
79
|
end
|
@@ -88,10 +81,9 @@ module Lab42
|
|
88
81
|
|
89
82
|
def _define_methods
|
90
83
|
(class << klass; self end).module_eval(&_define_freezing_constructor)
|
84
|
+
(class << klass; self end).module_eval(&_define_to_proc)
|
91
85
|
klass.module_eval(&_define_to_h)
|
92
86
|
klass.module_eval(&_define_merge)
|
93
|
-
klass.module_eval(&_define_eql?)
|
94
|
-
klass.module_eval(&block) if block
|
95
87
|
end
|
96
88
|
|
97
89
|
def _define_to_h
|
@@ -100,6 +92,15 @@ module Lab42
|
|
100
92
|
define_method :to_h do
|
101
93
|
proxy.to_hash(self)
|
102
94
|
end
|
95
|
+
alias_method :to_hash, :to_h
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def _define_to_proc
|
100
|
+
->(*) do
|
101
|
+
define_method :to_proc do
|
102
|
+
->(other) { self === other }
|
103
|
+
end
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
data/lib/lab42/data_class.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.3
|
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-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
An Immutable DataClass for Ruby
|
@@ -23,8 +23,8 @@ files:
|
|
23
23
|
- LICENSE
|
24
24
|
- README.md
|
25
25
|
- lib/lab42/data_class.rb
|
26
|
-
- lib/lab42/data_class/for_module.rb
|
27
26
|
- lib/lab42/data_class/proxy.rb
|
27
|
+
- lib/lab42/data_class/proxy/mixin.rb
|
28
28
|
- lib/lab42/data_class/version.rb
|
29
29
|
homepage: https://github.com/robertdober/lab42_data_class
|
30
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
|