lab42_data_class 0.2.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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