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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e882ef6623d30a0203797a53f31e4d3607e3d3e883adbb20ac63ee517db6c71e
4
- data.tar.gz: 3ce45a762f3b3628ea6ee19392b1a2202f3da5f55b8f4b9d341b25f4236581aa
3
+ metadata.gz: 8b6b4667c08da4511e2e4889bf35d21badc1018d7c8ce61efb66c65162c1ca0f
4
+ data.tar.gz: 0206dff912a52398e24488fd53aaac9c65ceceb3706b38bb562aac37d6d9d296
5
5
  SHA512:
6
- metadata.gz: a50fabe74deb22f46e0bc33c062547514fba922e138a3af2bbd6faeabe69f137a98ef5925166e04367366474bcfddf59d4c4067e81f0ebfc2ef61f86312702b0
7
- data.tar.gz: 61a6d7e6d7b4fca517622d49516cda9406daab3265b076eff18a0e65fa9ad60a188b295d302b64918600deef5c68d3a2684a6344e4bc2e96ff61dff9aa89d998
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, :has_parent, :members, :positionals
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 = kwds.fetch(:__klass__){ Class.new }
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, __klass__: self.class, **proxy.defaults, &proxy.block)
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.3"
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: Apache-2.0
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './data_class/for_module'
4
3
  require_relative './data_class/proxy'
5
4
 
6
5
  module Kernel
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.2.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-21 00:00:00.000000000 Z
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