data_class_factory 0.0.beta0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/data_class_factory.svg)](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: []
|