lite-data 0.0.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 +7 -0
- data/.rubocop.yml +72 -0
- data/Gemfile +12 -0
- data/README.md +63 -0
- data/bench/comparative.rb +85 -0
- data/lib/lite/data/definer/abstract.rb +33 -0
- data/lib/lite/data/definer/base.rb +84 -0
- data/lib/lite/data/definer/members/abstract.rb +45 -0
- data/lib/lite/data/definer/members/base.rb +65 -0
- data/lib/lite/data/definer/members/subclass.rb +63 -0
- data/lib/lite/data/definer/subclass.rb +69 -0
- data/lib/lite/data/error.rb +7 -0
- data/lib/lite/data/marker.rb +8 -0
- data/lib/lite/data/version.rb +9 -0
- data/lib/lite/data.rb +26 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d7ccf91b7250931a7aece3d9395a803039536854c4cceb244f8937bd8804446
|
4
|
+
data.tar.gz: 9dd190fdeecad8b5f699089350ce5a9934767edbdb1146cbdc8ff714adce2c7c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 789357cc7e35536f018b3bd61395d2ed513cca435f0bd3190e51f15e07692c732e4f571242c96f9ccbe571817fe90ce3a64c7a1483b1e17cb87eaddb1e2833bd
|
7
|
+
data.tar.gz: 8debe38ea903f30f960981bde64b8eaa5377934e717691314f042e331226770a4fd8e350ada9b0a0d5d1141432fabe97c0f96240c04fab32827139a242baa5d7
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
plugins:
|
2
|
+
rubocop-rspec
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
NewCops: enable
|
6
|
+
|
7
|
+
Style/Documentation:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/DocumentDynamicEvalDefinition:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Max: 20
|
15
|
+
|
16
|
+
Metrics/ModuleLength:
|
17
|
+
Max: 200
|
18
|
+
|
19
|
+
Metrics/BlockLength:
|
20
|
+
Exclude:
|
21
|
+
- spec/**/*.rb
|
22
|
+
|
23
|
+
Layout/LineLength:
|
24
|
+
Exclude:
|
25
|
+
- spec/**/*.rb
|
26
|
+
|
27
|
+
Lint/ConstantDefinitionInBlock:
|
28
|
+
Exclude:
|
29
|
+
- spec/**/*.rb
|
30
|
+
|
31
|
+
Lint/ShadowingOuterLocalVariable:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Layout/EndAlignment:
|
35
|
+
EnforcedStyleAlignWith: start_of_line
|
36
|
+
|
37
|
+
Rspec/MultipleExpectations:
|
38
|
+
Max: 3
|
39
|
+
|
40
|
+
Style/TrailingUnderscoreVariable:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Style/MultilineBlockChain:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Style/EachWithObject:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Style/HashSyntax:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Lint/BooleanSymbol:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/HashConversion:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Style/RedundantArrayConstructor:
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
Style/RedundantSelfAssignment:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
Style/MethodCallWithoutArgsParentheses:
|
65
|
+
Enabled: false
|
66
|
+
|
67
|
+
Style/ArgumentsForwarding:
|
68
|
+
Enabled: false
|
69
|
+
|
70
|
+
Naming/BlockForwarding:
|
71
|
+
Enabled: false
|
72
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Lite::Data
|
2
|
+
Easy definition of data classes with subclassing support
|
3
|
+
and flexible constructor signatures.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
Closely follows the interface of Ruby 3.2's `Data` class, but adds:
|
7
|
+
- Subclassing capability
|
8
|
+
- Mixed positional and keyword arguments
|
9
|
+
- Module-based design for including in existing classes
|
10
|
+
|
11
|
+
## Class definition
|
12
|
+
### Defining a data class
|
13
|
+
Use `Lite::Data.define(klass, args:, kwargs:)` with arrays for positional
|
14
|
+
and keyword parameters. The following declaration generates a module with
|
15
|
+
necessary instance methods and includes it in the target class:
|
16
|
+
|
17
|
+
```ruby rspec definition_superclass
|
18
|
+
class Foo
|
19
|
+
Lite::Data.define(self, args: [:foo])
|
20
|
+
end
|
21
|
+
|
22
|
+
expect(Foo.new('FOO').foo).to eq('FOO')
|
23
|
+
```
|
24
|
+
|
25
|
+
The same works when extending existing data classes:
|
26
|
+
```ruby rspec definition_subclass
|
27
|
+
class Bar < Foo
|
28
|
+
Lite::Data.define(self, kwargs: [:bar])
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(Bar.new('FOO', bar: 'BAR').bar).to eq('BAR')
|
32
|
+
```
|
33
|
+
|
34
|
+
### Argument positioning in subclasses
|
35
|
+
By default, new positional arguments are added at the start. Use `:'*'`
|
36
|
+
as a placeholder to control positioning:
|
37
|
+
|
38
|
+
```ruby rspec definition_argument_positioning
|
39
|
+
class Bax < Bar
|
40
|
+
Lite::Data.define(self, args: [:'*', :bax])
|
41
|
+
end
|
42
|
+
|
43
|
+
expect(Bax.new('FOO', 'BAX', bar: 'BAR').bax).to eq('BAX')
|
44
|
+
```
|
45
|
+
|
46
|
+
## Generated methods
|
47
|
+
Classes automatically receive:
|
48
|
+
- **Comparison**: `==`, `eql?`, `hash`
|
49
|
+
- **Cloning**: `with`
|
50
|
+
- **Introspection**: `deconstruct`, `deconstruct_keys`, `to_h`
|
51
|
+
|
52
|
+
### Destructuring order
|
53
|
+
The `deconstruct` method returns values in order of inheritance,
|
54
|
+
each class contributes its members in order of definition,
|
55
|
+
positional first:
|
56
|
+
```ruby rspec introspection_deconstruct
|
57
|
+
# inheritance chain: Bax < Bar < Foo
|
58
|
+
expect(Bax.new('FOO', 'BAX', bar: 'BAR').deconstruct)
|
59
|
+
.to eq(%w[FOO BAR BAX])
|
60
|
+
```
|
61
|
+
|
62
|
+
# License
|
63
|
+
This library is published under MIT license
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
require 'byebug'
|
5
|
+
|
6
|
+
require_relative '../lib/lite/data'
|
7
|
+
|
8
|
+
module Lite
|
9
|
+
module Data
|
10
|
+
module Benchmark
|
11
|
+
module Comparative
|
12
|
+
module Abstract
|
13
|
+
def instantiate(opts)
|
14
|
+
self::Data.new(**opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def clone(original, opts)
|
18
|
+
original.with(**opts.compact)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module RubyBase
|
23
|
+
extend Abstract
|
24
|
+
|
25
|
+
Data = ::Data.define(:foo, :bar, :bax, :qox)
|
26
|
+
end
|
27
|
+
|
28
|
+
module LiteBase
|
29
|
+
extend Abstract
|
30
|
+
|
31
|
+
class Data
|
32
|
+
Lite::Data.define(self, kwargs: %i[foo bar bax qox])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module LiteSubclass
|
37
|
+
extend Abstract
|
38
|
+
|
39
|
+
class Super
|
40
|
+
Lite::Data.define(self, kwargs: %i[foo bar])
|
41
|
+
end
|
42
|
+
|
43
|
+
class Data < Super
|
44
|
+
Lite::Data.define(self, kwargs: %i[bax qox])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
SYMBOLS = %i[a b c d e f g h i j].freeze
|
49
|
+
|
50
|
+
def self.run(n, duplicity: 0) # rubocop:disable Naming/MethodParameterName, Metrics/AbcSize
|
51
|
+
subjects = [RubyBase, LiteBase, LiteSubclass]
|
52
|
+
|
53
|
+
original_opts = opts
|
54
|
+
idata = 10_000.times.map { Random.rand < duplicity ? original_opts : opts }
|
55
|
+
cdata = idata.map(&:compact)
|
56
|
+
|
57
|
+
subjects.to_a.each do |subject|
|
58
|
+
result = ::Benchmark.measure do
|
59
|
+
n.times { |idx| subject.instantiate(idata[idx % idata.length]) }
|
60
|
+
end
|
61
|
+
puts "#{subject.name} NEW: #{result}"
|
62
|
+
|
63
|
+
original = subject.instantiate(original_opts)
|
64
|
+
result = ::Benchmark.measure do
|
65
|
+
n.times do |idx|
|
66
|
+
subject.clone(original, cdata[idx % cdata.length])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
puts "#{subject.name} CLONE: #{result}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.opts
|
74
|
+
{ foo: rand, bar: rand, bax: rand, qox: rand }
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.rand
|
78
|
+
SYMBOLS[Random.rand(11)]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Lite::Data::Benchmark::Comparative.run(100_000, duplicity: 0.1)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../marker'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Data
|
7
|
+
module Definer
|
8
|
+
module Abstract
|
9
|
+
def define(members)
|
10
|
+
Module.new do
|
11
|
+
extend Marker
|
12
|
+
|
13
|
+
attr_reader(*members.members)
|
14
|
+
|
15
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
16
|
+
def self.members
|
17
|
+
[#{members.members.map { ":#{_1}" }.join(', ')}]
|
18
|
+
end
|
19
|
+
|
20
|
+
def deconstruct
|
21
|
+
#{members.ivars_array}
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
{ #{members.hash_fields} }
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative 'members/base'
|
5
|
+
|
6
|
+
module Lite
|
7
|
+
module Data
|
8
|
+
module Definer
|
9
|
+
module Base
|
10
|
+
extend Abstract
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def deconstruct_keys(members)
|
14
|
+
case members
|
15
|
+
when nil then to_h
|
16
|
+
else to_h.slice(*members)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#<data #{self.class.name} #{to_h.map { |k, v| "#{k}=#{v.inspect}" }.join(', ')}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
inspect
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.define(positional_arguments, keyword_arguments) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
30
|
+
members = Members::Base.instance(positional_arguments, keyword_arguments)
|
31
|
+
mod = super(members)
|
32
|
+
|
33
|
+
mod.include InstanceMethods
|
34
|
+
|
35
|
+
mod.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
36
|
+
def initialize(#{members.initialize_signature})
|
37
|
+
#{members.initialize_ivars}
|
38
|
+
freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
return true if self.equal?(other)
|
43
|
+
return false unless self.class == other.class
|
44
|
+
|
45
|
+
#{members.equality}
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql?(other)
|
49
|
+
return true if self.equal?(other)
|
50
|
+
return false unless self.class == other.class
|
51
|
+
|
52
|
+
#{members.hash_equality}
|
53
|
+
end
|
54
|
+
|
55
|
+
def hash
|
56
|
+
[#{['self.class', *members.members].join(', ')}].hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def with(#{members.keyword_signature_defaults})
|
60
|
+
return self if #{members.variables_equal_attributes}
|
61
|
+
|
62
|
+
self.class.send(#{[':new', *members.constructor_arguments].join(', ')})
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def merged_constructor_arguments(#{members.merged_constructor_arguments_signature})
|
68
|
+
identical &&= #{members.variables_equal_attributes}
|
69
|
+
return identical if identical
|
70
|
+
|
71
|
+
[false, #{members.merged_constructor_arguments}]
|
72
|
+
end
|
73
|
+
RUBY
|
74
|
+
|
75
|
+
mod
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.define_class_methods(base, mod)
|
79
|
+
base.define_singleton_method(:members) { mod.members }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../error'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Data
|
7
|
+
module Definer
|
8
|
+
module Members
|
9
|
+
class Abstract
|
10
|
+
def self.instance(positional_arguments, keyword_arguments, members)
|
11
|
+
ensure_members_valid!(members)
|
12
|
+
new positional_arguments, keyword_arguments, members
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.ensure_members_valid!(members)
|
16
|
+
raise Error, 'Members must not be empty' if members.empty?
|
17
|
+
|
18
|
+
invalid = members.reject { _1.is_a?(Symbol) }
|
19
|
+
raise Error, "Array of symbols expected, got: #{invalid.map(&:inspect).join(', ')}" unless invalid.empty?
|
20
|
+
|
21
|
+
uniq = members.uniq
|
22
|
+
raise Error, 'Member names must be unique' unless uniq.length == members.length
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(positional_arguments, keyword_arguments, members)
|
26
|
+
@positional_arguments = positional_arguments.freeze
|
27
|
+
@keyword_arguments = keyword_arguments.freeze
|
28
|
+
@members = members.freeze
|
29
|
+
freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :positional_arguments, :keyword_arguments, :members
|
33
|
+
|
34
|
+
def initialize_ivars
|
35
|
+
members.map { "@#{_1} = #{_1}" }.join(';')
|
36
|
+
end
|
37
|
+
|
38
|
+
def variables_equal_attributes
|
39
|
+
members.map { "#{_1} == @#{_1}" }.join(' && ')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Data
|
7
|
+
module Definer
|
8
|
+
module Members
|
9
|
+
class Base < Abstract
|
10
|
+
def self.instance(positional_arguments, keyword_arguments)
|
11
|
+
super(
|
12
|
+
positional_arguments,
|
13
|
+
keyword_arguments,
|
14
|
+
positional_arguments + keyword_arguments
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_signature
|
19
|
+
(positional_arguments + keyword_arguments.map { "#{_1}:" }).join(', ')
|
20
|
+
end
|
21
|
+
|
22
|
+
def keyword_signature_defaults
|
23
|
+
members.map { "#{_1}: @#{_1}" }.join(', ')
|
24
|
+
end
|
25
|
+
|
26
|
+
def constructor_arguments
|
27
|
+
(positional_arguments + keyword_arguments.map { "#{_1}: #{_1}" }).join(', ')
|
28
|
+
end
|
29
|
+
|
30
|
+
def ivars_array
|
31
|
+
"[#{members.map { " @#{_1}" }.join(', ')}]"
|
32
|
+
end
|
33
|
+
|
34
|
+
def positional_arguments_array
|
35
|
+
"[#{positional_arguments.join(', ')}]"
|
36
|
+
end
|
37
|
+
|
38
|
+
def keyword_arguments_hash
|
39
|
+
"{ #{keyword_arguments.map { "#{_1}: #{_1}" }.join(', ')} }"
|
40
|
+
end
|
41
|
+
|
42
|
+
def merged_constructor_arguments_signature
|
43
|
+
['identical', members.map { "#{_1}: @#{_1}" }].join(', ')
|
44
|
+
end
|
45
|
+
|
46
|
+
def merged_constructor_arguments
|
47
|
+
"#{positional_arguments_array}, #{keyword_arguments_hash}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def hash_fields
|
51
|
+
members.map { "#{_1}: @#{_1}" }.join(', ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def equality
|
55
|
+
members.map { "#{_1} == other.#{_1}" }.join(' && ')
|
56
|
+
end
|
57
|
+
|
58
|
+
def hash_equality
|
59
|
+
members.map { "#{_1}.eql?(other.#{_1})" }.join(' && ')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
|
5
|
+
module Lite
|
6
|
+
module Data
|
7
|
+
module Definer
|
8
|
+
module Members
|
9
|
+
class Subclass < Abstract
|
10
|
+
def self.instance(positional_arguments, keyword_arguments)
|
11
|
+
positional_arguments = [*positional_arguments, :*] unless positional_arguments.include? :*
|
12
|
+
|
13
|
+
super(
|
14
|
+
positional_arguments,
|
15
|
+
keyword_arguments,
|
16
|
+
(positional_arguments.reject { _1 == :* } + keyword_arguments)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :positional_arguments, :keyword_arguments, :members
|
21
|
+
|
22
|
+
def interpolated_positional_arguments(replacement)
|
23
|
+
positional_arguments.map { _1 == :* ? replacement : _1 }
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize_signature
|
27
|
+
[
|
28
|
+
*interpolated_positional_arguments('*args'),
|
29
|
+
*keyword_arguments.map { "#{_1}:" },
|
30
|
+
'**opts'
|
31
|
+
].join(', ')
|
32
|
+
end
|
33
|
+
|
34
|
+
def ivars_array
|
35
|
+
"[*super, #{members.map { "@#{_1}" }.join(', ')}]"
|
36
|
+
end
|
37
|
+
|
38
|
+
def merged_constructor_arguments_signature
|
39
|
+
['identical', members.map { "#{_1}: @#{_1}" }, '**opts'].join(', ')
|
40
|
+
end
|
41
|
+
|
42
|
+
def merged_constructor_arguments
|
43
|
+
positional = interpolated_positional_arguments('*args').join(', ')
|
44
|
+
keyword = [*keyword_arguments.map { "#{_1}: #{_1}" }, '**opts'].join(', ')
|
45
|
+
"[#{positional}], { #{keyword} }"
|
46
|
+
end
|
47
|
+
|
48
|
+
def hash_fields
|
49
|
+
['**super', *members.map { "#{_1}: @#{_1}" }].join(', ')
|
50
|
+
end
|
51
|
+
|
52
|
+
def equality
|
53
|
+
['super', *members.map { "#{_1} == other.#{_1}" }].join(' && ')
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash_equality
|
57
|
+
['super', *members.map { "#{_1}.eql?(other.#{_1})" }].join(' && ')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'abstract'
|
4
|
+
require_relative 'members/subclass'
|
5
|
+
|
6
|
+
module Lite
|
7
|
+
module Data
|
8
|
+
module Definer
|
9
|
+
module Subclass
|
10
|
+
extend Abstract
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def with(**opts)
|
14
|
+
return self if opts.empty?
|
15
|
+
|
16
|
+
identical, args, opts = merged_constructor_arguments(true, **opts)
|
17
|
+
return self if identical
|
18
|
+
|
19
|
+
self.class.send(:new, *args, **opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.define(positional_arguments, keyword_arguments) # rubocop:disable Metrics/MethodLength
|
24
|
+
members = Members::Subclass.instance(positional_arguments, keyword_arguments)
|
25
|
+
mod = super(members)
|
26
|
+
|
27
|
+
mod.include InstanceMethods
|
28
|
+
|
29
|
+
mod.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
30
|
+
def initialize(#{members.initialize_signature})
|
31
|
+
#{members.initialize_ivars}
|
32
|
+
super *args, **opts
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
#{members.equality}
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql?(other)
|
40
|
+
#{members.hash_equality}
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
[#{['super', *members.members].join(', ')}].hash
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def merged_constructor_arguments(#{members.merged_constructor_arguments_signature})
|
50
|
+
identical &&= #{members.variables_equal_attributes}
|
51
|
+
|
52
|
+
return true if identical && opts.empty?
|
53
|
+
identical, args, opts = super(identical, **opts)
|
54
|
+
return true if identical
|
55
|
+
|
56
|
+
[false, #{members.merged_constructor_arguments}]
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
|
60
|
+
mod
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.define_class_methods(base, mod)
|
64
|
+
base.define_singleton_method(:members) { super() + mod.members }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/lite/data.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'data/definer/base'
|
4
|
+
require_relative 'data/definer/subclass'
|
5
|
+
|
6
|
+
module Lite
|
7
|
+
module Data
|
8
|
+
private_constant :Definer
|
9
|
+
|
10
|
+
def self.define(base, args: [], kwargs: [])
|
11
|
+
supermod = base.ancestors.find { |klass| klass.is_a?(Marker) }
|
12
|
+
prevent_conflicts!(base, args, kwargs) if supermod
|
13
|
+
|
14
|
+
definer = supermod ? Definer::Subclass : Definer::Base
|
15
|
+
mod = definer.define(args, kwargs)
|
16
|
+
definer.define_class_methods(base, mod)
|
17
|
+
base.include(mod)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.prevent_conflicts!(klass, args, kwargs)
|
22
|
+
duplicates = klass.members & (args + kwargs)
|
23
|
+
raise Error, "Members already declared: #{duplicates.map(&:inspect).join(', ')}" unless duplicates.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lite-data
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomas Milsimer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-09-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
Easy definition of data classes with subclassing support
|
15
|
+
and flexible constructor signatures.
|
16
|
+
email:
|
17
|
+
- tomas.milsimer@protonmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- Gemfile
|
24
|
+
- README.md
|
25
|
+
- bench/comparative.rb
|
26
|
+
- lib/lite/data.rb
|
27
|
+
- lib/lite/data/definer/abstract.rb
|
28
|
+
- lib/lite/data/definer/base.rb
|
29
|
+
- lib/lite/data/definer/members/abstract.rb
|
30
|
+
- lib/lite/data/definer/members/base.rb
|
31
|
+
- lib/lite/data/definer/members/subclass.rb
|
32
|
+
- lib/lite/data/definer/subclass.rb
|
33
|
+
- lib/lite/data/error.rb
|
34
|
+
- lib/lite/data/marker.rb
|
35
|
+
- lib/lite/data/version.rb
|
36
|
+
homepage: https://github.com/lame-impala/lite-data
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
rubygems_mfa_required: 'true'
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 3.0.0
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubygems_version: 3.4.10
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: Minimalistic data-class definition
|
60
|
+
test_files: []
|