lab42_data_class 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 03bada220d0a003c294685e37c3276b15786353f753b60cbafdaf6c78e3f986c
4
+ data.tar.gz: d36b51077068701de6ecd9db90831d6290b2620d4d403402403ad4af3f1420b2
5
+ SHA512:
6
+ metadata.gz: 931c7c48bd60f8cfdc8ae711b292facf18622de31fbf7d04116af68a70c559171384a024ef628195789a9c25121db5c10879ffc36d99d81fb71cbcd88e25baca
7
+ data.tar.gz: b2924660d7b0476092a9c85bd4d35c26fb59d4365d1a3f0ff2021343b336bdc28dd5a60dfd9fefb49a9f150cd0c955f71da0d896d72c17f37aa9a6f4352aff3f
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright © 2014 Dave Thomas, The Pragmatic Programmers
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ https://spdx.org/licenses/Apache-2.0.html
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+ Note:
16
+ Individual files contain the following tag instead of the full license text.
17
+
18
+ SPDX-License-Identifier: Apache-2.0
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+
2
+ [![Gem Version](http://img.shields.io/gem/v/lab42_data_class.svg)](https://rubygems.org/gems/lab42_data_class)
3
+ [![CI](https://github.com/robertdober/lab42_data_class/workflows/CI/badge.svg)](https://github.com/robertdober/lab42_data_class/actions)
4
+ [![Coverage Status](https://coveralls.io/repos/github/RobertDober/lab42_data_class/badge.svg?branch=main)](https://coveralls.io/github/RobertDober/lab42_data_class?branch=main)
5
+
6
+
7
+ # Lab42::DataClass
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!)
10
+
11
+ ## So what does it do?
12
+
13
+ Well let us [speculate about](https://github.com/RobertDober/speculate_about) it to find out:
14
+
15
+ ### Context: `DataClass` function
16
+
17
+ Given
18
+ ```ruby
19
+ let(:my_data_class) { DataClass(:name, email: nil) }
20
+ let(:my_instance) { my_data_class.new(name: "robert") }
21
+ ```
22
+
23
+ Then we can access its fields
24
+ ```ruby
25
+ expect(my_instance.name).to eq("robert")
26
+ expect(my_instance.email).to be_nil
27
+ ```
28
+
29
+ But we cannot access undefined fields
30
+ ```ruby
31
+ expect{ my_instance.undefined }.to raise_error(NoMethodError)
32
+ ```
33
+
34
+ And we need to provide values to fields without defaults
35
+ ```ruby
36
+ expect{ my_data_class.new(email: "some@mail.org") }
37
+ .to raise_error(ArgumentError, "missing initializers for [:name]")
38
+ ```
39
+ And we can extract the values
40
+ ```ruby
41
+ expect(my_instance.to_h).to eq(name: "robert", email: nil)
42
+ ```
43
+
44
+ #### Context: Immutable
45
+
46
+ Given
47
+ ```ruby
48
+ let(:other_instance) { my_instance.merge(email: "robert@mail.provider") }
49
+ ```
50
+ Then we have a new instance with the old instance unchanged
51
+ ```ruby
52
+ expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
53
+ expect(my_instance.to_h).to eq(name: "robert", email: nil)
54
+ ```
55
+
56
+ ### Context: Defining behavior with blocks
57
+
58
+ Given
59
+ ```ruby
60
+ let :my_data_class do
61
+ DataClass :value, prefix: "<", suffix: ">" do
62
+ def show
63
+ [prefix, value, suffix].join
64
+ end
65
+ end
66
+ end
67
+ let(:my_instance) { my_data_class.new(value: 42) }
68
+ ```
69
+
70
+ Then I have defined a method on my dataclass
71
+ ```ruby
72
+ expect(my_instance.show).to eq("<42>")
73
+ ```
74
+ ### Context: Making a dataclass from a class
75
+
76
+ Given
77
+ ```ruby
78
+ class DC
79
+ dataclass x: 1, y: 41
80
+ def sum; x + y end
81
+ end
82
+ ```
83
+
84
+ Then we can define methods on it
85
+ ```ruby
86
+ expect(DC.new.sum).to eq(42)
87
+ ```
88
+
89
+ And we have a nice name for our instances
90
+ ```ruby
91
+ expect(DC.new.class.name).to eq("DC")
92
+ ```
93
+
94
+ # LICENSE
95
+
96
+ Copyright 2022 Robert Dober robert.dober@gmail.com
97
+
98
+ Apache-2.0 [c.f LICENSE](LICENSE)
99
+ <!-- SPDX-License-Identifier: Apache-2.0-->
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ class Proxy
6
+ attr_reader :actual_params, :block, :defaults, :klass, :has_parent, :members, :positionals
7
+
8
+ def check!(**params)
9
+ @actual_params = params
10
+ raise ArgumentError, "missing initializers for #{_missing_initializers}" unless _missing_initializers.empty?
11
+ raise ArgumentError, "illegal initializers #{_illegal_initializers}" unless _illegal_initializers.empty?
12
+ end
13
+
14
+ def define_class!
15
+ klass.module_eval(&_define_attr_reader)
16
+ klass.module_eval(&_define_initializer)
17
+ _define_methods
18
+ klass
19
+ end
20
+
21
+ def init(data_class, **params)
22
+ _init(data_class, defaults.merge(params))
23
+ end
24
+
25
+ def to_hash(data_class_instance)
26
+ members
27
+ .map { [_1, data_class_instance.instance_variable_get("@#{_1}")] }
28
+ .to_h
29
+ end
30
+
31
+ private
32
+ def initialize(*args, **kwds, &blk)
33
+ @klass = kwds.fetch(:__klass__){ Class.new }
34
+ @has_parent = !!kwds.delete(:__klass__)
35
+
36
+ @block = blk
37
+ @defaults = kwds
38
+ @members = Set.new(args + kwds.keys)
39
+ # TODO: Check for all symbols and no duplicates ⇒ v0.1.1
40
+ @positionals = args
41
+ end
42
+
43
+ def _define_attr_reader
44
+ proxy = self
45
+ ->(*) do
46
+ attr_reader(*proxy.members)
47
+ end
48
+ end
49
+
50
+ def _define_initializer
51
+ proxy = self
52
+ ->(*) do
53
+ define_method :initialize do |**params|
54
+ proxy.check!(**params)
55
+ proxy.init(self, **params)
56
+ end
57
+ end
58
+ end
59
+
60
+ def _define_merge
61
+ proxy = self
62
+ ->(*) do
63
+ define_method :merge do |**params|
64
+ values = to_h.merge(params)
65
+ DataClass(*proxy.positionals, __klass__: self.class, **proxy.defaults, &proxy.block)
66
+ .new(**values)
67
+ end
68
+ end
69
+ end
70
+
71
+ def _define_methods
72
+ klass.module_eval(&_define_to_h)
73
+ klass.module_eval(&_define_merge)
74
+ klass.module_eval(&block) if block
75
+ end
76
+
77
+ def _define_to_h
78
+ proxy = self
79
+ ->(*) do
80
+ define_method :to_h do
81
+ proxy.to_hash(self)
82
+ end
83
+ end
84
+ end
85
+
86
+ def _init(data_class_instance, params)
87
+ params.each do |key, value|
88
+ data_class_instance.instance_variable_set("@#{key}", value)
89
+ end
90
+ end
91
+
92
+ def _missing_initializers
93
+ @___missing_initializers__ ||=
94
+ positionals - actual_params.keys
95
+ end
96
+
97
+ def _illegal_initializers
98
+ @___illegal_initializers__ ||=
99
+ actual_params.keys - positionals - defaults.keys
100
+ end
101
+ end
102
+ end
103
+ end
104
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab42
4
+ module DataClass
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './data_class/for_module'
4
+ require_relative './data_class/proxy'
5
+
6
+ module Kernel
7
+ def DataClass(*args, **kwds, &blk)
8
+ proxy = Lab42::DataClass::Proxy.new(*args, **kwds, &blk)
9
+ proxy.define_class!
10
+ end
11
+ end
12
+
13
+ # SPDX-License-Identifier: Apache-2.0
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lab42_data_class
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: introduces a new Kernel function DataClass
14
+ email: robert.dober@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - lib/lab42/data_class.rb
22
+ - lib/lab42/data_class/for_module.rb
23
+ - lib/lab42/data_class/proxy.rb
24
+ - lib/lab42/data_class/version.rb
25
+ homepage: https://github.com/robertdober/lab42_data_class
26
+ licenses:
27
+ - Apache-2.0
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 3.1.0
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.3.3
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Finally a dataclass in ruby
48
+ test_files: []