lab42_data_class 0.1.0 → 0.3.0

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: 03bada220d0a003c294685e37c3276b15786353f753b60cbafdaf6c78e3f986c
4
- data.tar.gz: d36b51077068701de6ecd9db90831d6290b2620d4d403402403ad4af3f1420b2
3
+ metadata.gz: 3c062e2633e5881141e65113d2d60878733611bece697c933876ef1d9f62270e
4
+ data.tar.gz: bb5e524274338fda67063a8514cbe17a27c92ec8b62106061793456245729e30
5
5
  SHA512:
6
- metadata.gz: 931c7c48bd60f8cfdc8ae711b292facf18622de31fbf7d04116af68a70c559171384a024ef628195789a9c25121db5c10879ffc36d99d81fb71cbcd88e25baca
7
- data.tar.gz: b2924660d7b0476092a9c85bd4d35c26fb59d4365d1a3f0ff2021343b336bdc28dd5a60dfd9fefb49a9f150cd0c955f71da0d896d72c17f37aa9a6f4352aff3f
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
- A dataclass with an immutable API (you can still change the state of the object with metaprogramming and `lab42_immutable` is not ready yet!)
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
- Given
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
- class DC
79
- dataclass x: 1, y: 41
80
- def sum; x + y end
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 can define methods on it
153
+ Then we have reused the `token` successfully
85
154
  ```ruby
86
- expect(DC.new.sum).to eq(42)
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
- And we have a nice name for our instances
164
+ #### Context: Mixing in a module can be used of course
165
+
166
+ Given a behavior like
90
167
  ```ruby
91
- expect(DC.new.class.name).to eq("DC")
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, :has_parent, :members, :positionals
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 = kwds.fetch(:__klass__){ Class.new }
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, __klass__: self.class, **proxy.defaults, &proxy.block)
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lab42
4
4
  module DataClass
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
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,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.1.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-20 00:00:00.000000000 Z
11
+ date: 2022-01-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: introduces a new Kernel function DataClass
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