data_class_factory 0.0.beta0 → 0.1.0
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 +4 -4
- data/Gemfile +4 -2
- data/README.md +46 -17
- data/Rakefile +7 -5
- data/bin/console +4 -3
- data/data_class_factory.gemspec +16 -12
- data/lib/data.rb +7 -0
- data/lib/data_class/definition.rb +43 -0
- data/lib/data_class/factory.rb +29 -0
- data/lib/data_class/instance_methods.rb +120 -0
- data/lib/data_class_factory/version.rb +3 -1
- data/lib/data_class_factory.rb +37 -3
- metadata +52 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85a61ac673bb16b61d606b2a332aada35a31ae1e62d0db7140fbe88f1598d84a
|
4
|
+
data.tar.gz: c0533d7643ef9eaca60995f7d35797179f55471f883fb15c943e9ef53a1d95a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcb368e9071a14ba6da85043a57d46897ab9ee016e9587ca674964c8a39858a6be0e476f47db94718db761efd4896e0b3db156eca1457ca6294e5b8fdb535b22
|
7
|
+
data.tar.gz: 1f5ca0c39dbd680fd81b6587fce069a05f5653f6b898e806a22a7ca3831572596f1218dff47bd898994492ae447b38e8fc58dcdb134b8c73e0d8b457948c2ed9
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in data_class_factory.gemspec
|
6
8
|
gemspec
|
data/README.md
CHANGED
@@ -1,38 +1,67 @@
|
|
1
|
-
|
1
|
+
[](https://badge.fury.io/rb/data_class_factory)
|
2
2
|
|
3
|
-
|
3
|
+
# Data class factory
|
4
4
|
|
5
|
-
|
5
|
+
Backport `Data.define` for Ruby 2.7, 3.0, 3.1 :)
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
|
-
Add this line to your application's Gemfile:
|
10
|
-
|
11
9
|
```ruby
|
12
10
|
gem 'data_class_factory'
|
13
11
|
```
|
14
12
|
|
15
|
-
|
13
|
+
and then `bundle install`
|
16
14
|
|
17
|
-
|
15
|
+
## Usage
|
18
16
|
|
19
|
-
|
17
|
+
```ruby
|
18
|
+
require 'data_class_factory'
|
19
|
+
|
20
|
+
Point = Data.define(:x, :y) do
|
21
|
+
def norm
|
22
|
+
Math.sqrt(x * x + y * y)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
p1 = Point.new(x: 3, y: 4)
|
26
|
+
p2 = Point.new(x: 3, y: 4)
|
27
|
+
p1 == p2 # => true
|
28
|
+
```
|
20
29
|
|
21
|
-
|
30
|
+
Most features of the data class are ported. See https://docs.ruby-lang.org/en/3.2/Data.html for checking the original spec of `Data`.
|
22
31
|
|
23
|
-
|
32
|
+
One difference is that backported Data class intentionally accepts only keyword arguments for `.new` while original Data class accepts both positional args and keyword args.
|
24
33
|
|
25
|
-
|
34
|
+
```ruby
|
35
|
+
Point = Data.define(:x, :y) do
|
36
|
+
def norm
|
37
|
+
Math.sqrt(x * x + y * y)
|
38
|
+
end
|
39
|
+
end
|
26
40
|
|
27
|
-
|
41
|
+
# raises ArgumentError
|
42
|
+
# wrong number of arguments (given 2, expected 0)
|
43
|
+
p1 = Point.new(3, 4)
|
28
44
|
|
29
|
-
|
45
|
+
p1 = Point.new(x: 3, y: 4) # works well :)
|
46
|
+
```
|
30
47
|
|
31
|
-
|
48
|
+
### aliasing
|
32
49
|
|
33
|
-
|
50
|
+
REMARK this gem removes `Data` [defined on Ruby 2.7](https://ruby-doc.org/core-2.7.0/Data.html), and respects original `Data` on Ruby >= 3.2. If you prefer using this gem also on Ruby >= 3.2, choose another name like this.
|
34
51
|
|
35
|
-
|
52
|
+
```ruby
|
53
|
+
MyDataClass = DataClassFactory.define_factory_class
|
54
|
+
```
|
55
|
+
|
56
|
+
We can use the aliased class as usual like below.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Point = MyDataClass.define(:x, :y) do
|
60
|
+
def norm
|
61
|
+
Math.sqrt(x * x + y * y)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
36
65
|
|
37
66
|
## License
|
38
67
|
|
@@ -40,4 +69,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
69
|
|
41
70
|
## Code of Conduct
|
42
71
|
|
43
|
-
Everyone interacting in the DataClassFactory project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](
|
72
|
+
Everyone interacting in the DataClassFactory project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
3
5
|
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
7
|
-
t.test_files = FileList[
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
10
|
end
|
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'data_class_factory'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "data_class_factory"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
data/data_class_factory.gemspec
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require 'data_class_factory/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'data_class_factory'
|
8
9
|
spec.version = DataClassFactory::VERSION
|
9
|
-
spec.authors = [
|
10
|
+
spec.authors = ['YusukeIwaki']
|
10
11
|
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage =
|
13
|
-
spec.license =
|
12
|
+
spec.summary = 'Backport `Data.define` for Ruby 2.7, 3.0, 3.1'
|
13
|
+
spec.homepage = 'https://github.com/YusukeIwaki/data_class_factory'
|
14
|
+
spec.license = 'MIT'
|
14
15
|
|
15
16
|
# Specify which files should be added to the gem when it is released.
|
16
17
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -19,12 +20,15 @@ Gem::Specification.new do |spec|
|
|
19
20
|
f.match(%r{^(test|spec|features)/}) || f.include?('.git')
|
20
21
|
end
|
21
22
|
end
|
22
|
-
spec.bindir =
|
23
|
+
spec.bindir = 'exe'
|
23
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
-
spec.require_paths = [
|
25
|
+
spec.require_paths = ['lib']
|
25
26
|
|
26
27
|
spec.required_ruby_version = '>= 2.7'
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
28
|
+
spec.add_development_dependency 'bundler'
|
29
|
+
spec.add_development_dependency 'minitest'
|
30
|
+
spec.add_development_dependency 'pry'
|
31
|
+
spec.add_development_dependency 'rake'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'rubocop-minitest'
|
30
34
|
end
|
data/lib/data.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module DataClass
|
6
|
+
# An internal class for providing validation of `Data.define` and its initializer.
|
7
|
+
class Definition
|
8
|
+
# @param attribute_names [Array<Symbol>]
|
9
|
+
def initialize(attribute_names)
|
10
|
+
validate_attribute_names(attribute_names)
|
11
|
+
@attribute_names = attribute_names.each { |key| validate_attribute_name(key) }
|
12
|
+
end
|
13
|
+
attr_reader :attribute_names
|
14
|
+
|
15
|
+
# @param kwargs [Hash<Symbol, Object>]
|
16
|
+
def validate(kwargs)
|
17
|
+
if attribute_names - kwargs.keys != []
|
18
|
+
raise ArgumentError, "missing keyword: #{(attribute_names - kwargs.keys).join(', ')}"
|
19
|
+
end
|
20
|
+
|
21
|
+
if kwargs.keys - attribute_names != []
|
22
|
+
raise ArgumentError, "unknown keyword: #{(kwargs.keys - attribute_names).join(', ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_attribute_names(attribute_names)
|
31
|
+
checked = Set.new
|
32
|
+
attribute_names.each do |key|
|
33
|
+
raise TypeError, "#{key} is not a symbol" unless key.is_a?(Symbol)
|
34
|
+
raise ArgumentError, "invalid data member: #{key}" if key.end_with?('=')
|
35
|
+
raise ArgumentError, "duplicate member: #{key}" if checked.include?(key)
|
36
|
+
|
37
|
+
checked << key
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_attribute_name(key); end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DataClass
|
4
|
+
# An internal class for providing implementation of `Data.define`.
|
5
|
+
class Factory
|
6
|
+
# @param attribute_names [Array<Symbol>]
|
7
|
+
def initialize(attribute_names)
|
8
|
+
@definition = Definition.new(attribute_names)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param parent_class [Data]
|
12
|
+
# @return [Class<Data>]
|
13
|
+
def create(parent_class:, &block)
|
14
|
+
attribute_names = @definition.attribute_names
|
15
|
+
|
16
|
+
# defines a subclass of Data.
|
17
|
+
Class.new(parent_class) do
|
18
|
+
public_class_method :new
|
19
|
+
private_class_method :define
|
20
|
+
|
21
|
+
attribute_names.each { |key| define_method(key) { @data[key] } }
|
22
|
+
|
23
|
+
define_singleton_method(:members) { attribute_names }
|
24
|
+
|
25
|
+
class_eval(&block) unless block.nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DataClass
|
4
|
+
# An internal module for providing instance methods for `Data.define`.
|
5
|
+
module InstanceMethods
|
6
|
+
def initialize(**kwargs)
|
7
|
+
definition = DataClass::Definition.new(self.class.members)
|
8
|
+
definition.validate(kwargs)
|
9
|
+
|
10
|
+
@data = kwargs.each_with_object({}) do |entry, h|
|
11
|
+
key, value = entry
|
12
|
+
h[key] = value
|
13
|
+
end.freeze
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_copy(other)
|
18
|
+
@data = other.instance_variable_get(:@data).dup
|
19
|
+
@data.freeze
|
20
|
+
freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def deconstruct
|
24
|
+
@data.values
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param array_of_names_or_nil [Array<Symbol>, nil]
|
28
|
+
def deconstruct_keys(array_of_names_or_nil)
|
29
|
+
return to_h if array_of_names_or_nil.nil?
|
30
|
+
|
31
|
+
unless array_of_names_or_nil.is_a?(Enumerable)
|
32
|
+
raise TypeError, "wrong argument type #{array_of_names_or_nil.class} (expected Array or nil)"
|
33
|
+
end
|
34
|
+
|
35
|
+
array_of_names_or_nil.each_with_object({}) do |key, h|
|
36
|
+
h[key] = @data[key] if @data[key]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Boolean]
|
41
|
+
def eql?(other)
|
42
|
+
hash_for_comparation.eql?(other.hash_for_comparation)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Integer]
|
46
|
+
def hash
|
47
|
+
hash_for_comparation.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String]
|
51
|
+
def inspect
|
52
|
+
if self.class.name
|
53
|
+
"#<data #{self.class.name} #{inspect_members}>"
|
54
|
+
else
|
55
|
+
"#<data #{inspect_members}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Hash]
|
60
|
+
def marshal_dump
|
61
|
+
@data
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param dump [Hash]
|
65
|
+
def marshal_load(dump)
|
66
|
+
raise TypeError, 'dump must be a Hash' unless dump.is_a?(Hash)
|
67
|
+
|
68
|
+
initialize(**dump)
|
69
|
+
end
|
70
|
+
|
71
|
+
def members
|
72
|
+
self.class.members
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_h(&block)
|
76
|
+
@data.each_with_object({}) do |key_and_value, h|
|
77
|
+
key, value = block ? block.call(*key_and_value) : key_and_value
|
78
|
+
h[key] = value
|
79
|
+
end.to_h
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
inspect
|
84
|
+
end
|
85
|
+
|
86
|
+
def with(**kwargs)
|
87
|
+
return self if kwargs.empty?
|
88
|
+
|
89
|
+
unknown_keywords = kwargs.keys - @data.keys
|
90
|
+
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(', ')}" unless unknown_keywords.empty?
|
91
|
+
|
92
|
+
new_data = @data.merge(kwargs)
|
93
|
+
self.class.new(**new_data)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Boolean]
|
97
|
+
def ==(other)
|
98
|
+
hash_for_comparation == other.hash_for_comparation
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def hash_for_comparation
|
104
|
+
{ type: self.class, data: @data }
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# @return [String]
|
110
|
+
def inspect_members
|
111
|
+
@data.map do |key, value|
|
112
|
+
if key =~ /\A[a-zA-Z_][a-zA-Z0-9_]*\z/
|
113
|
+
"#{key}=#{value.inspect}"
|
114
|
+
else
|
115
|
+
"#{key.inspect}=#{value.inspect}"
|
116
|
+
end
|
117
|
+
end.join(', ')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/data_class_factory.rb
CHANGED
@@ -1,6 +1,40 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'data_class_factory/version'
|
4
|
+
require 'data_class/definition'
|
5
|
+
require 'data_class/factory'
|
6
|
+
require 'data_class/instance_methods'
|
7
|
+
|
8
|
+
# Defining a factory class.
|
9
|
+
# `Data = DataClassFactory.define_factory_class` will define a factory class like this.
|
10
|
+
#
|
11
|
+
# class Data
|
12
|
+
# # @param attribute_names [Array<Symbol>]
|
13
|
+
# # @return [Class<Data>]
|
14
|
+
# def self.define(*attribute_names, &block)
|
15
|
+
# factory = DataClass::Factory.new(attribute_names)
|
16
|
+
# factory.create(parent_class: self, &block)
|
17
|
+
# end
|
18
|
+
# ...
|
19
|
+
# end
|
20
|
+
#
|
3
21
|
module DataClassFactory
|
4
|
-
|
5
|
-
|
22
|
+
def backport?
|
23
|
+
Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2.0')
|
24
|
+
end
|
25
|
+
|
26
|
+
def define_factory_class
|
27
|
+
Class.new do
|
28
|
+
define_singleton_method(:define) do |*attribute_names, &block|
|
29
|
+
factory = DataClass::Factory.new(attribute_names)
|
30
|
+
factory.create(parent_class: self, &block)
|
31
|
+
end
|
32
|
+
private_class_method :new
|
33
|
+
|
34
|
+
include DataClass::InstanceMethods
|
35
|
+
end
|
36
|
+
end
|
37
|
+
module_function :backport?, :define_factory_class
|
6
38
|
end
|
39
|
+
|
40
|
+
require_relative './data'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: data_class_factory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- YusukeIwaki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +67,21 @@ dependencies:
|
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-minitest
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
44
86
|
requirements:
|
45
87
|
- - ">="
|
@@ -66,6 +108,10 @@ files:
|
|
66
108
|
- bin/console
|
67
109
|
- bin/setup
|
68
110
|
- data_class_factory.gemspec
|
111
|
+
- lib/data.rb
|
112
|
+
- lib/data_class/definition.rb
|
113
|
+
- lib/data_class/factory.rb
|
114
|
+
- lib/data_class/instance_methods.rb
|
69
115
|
- lib/data_class_factory.rb
|
70
116
|
- lib/data_class_factory/version.rb
|
71
117
|
homepage: https://github.com/YusukeIwaki/data_class_factory
|
@@ -83,12 +129,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
129
|
version: '2.7'
|
84
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
131
|
requirements:
|
86
|
-
- - "
|
132
|
+
- - ">="
|
87
133
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
134
|
+
version: '0'
|
89
135
|
requirements: []
|
90
136
|
rubygems_version: 3.4.6
|
91
137
|
signing_key:
|
92
138
|
specification_version: 4
|
93
|
-
summary: Backport `Data.define` for Ruby
|
139
|
+
summary: Backport `Data.define` for Ruby 2.7, 3.0, 3.1
|
94
140
|
test_files: []
|