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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13db8d588b7024e13dd87db5fade13f9b00c87a6ba480d43361ea51dc9494296
4
- data.tar.gz: 3b1f1c66790b23e8fd87557afb30979655af70687fff44c459707716ba25f5de
3
+ metadata.gz: 85a61ac673bb16b61d606b2a332aada35a31ae1e62d0db7140fbe88f1598d84a
4
+ data.tar.gz: c0533d7643ef9eaca60995f7d35797179f55471f883fb15c943e9ef53a1d95a6
5
5
  SHA512:
6
- metadata.gz: a84caf0dcadf17210bdd61a4ea284c1b2d54a1c4231ae2a72feca44a654ea52f4e8cdc0d6af64981d6f2b10cbfca1938487ff7bb2f934a3f6156d2253ac9d406
7
- data.tar.gz: 06c62651ce5e6de2c63666b084f18010d5b1ce5bcdd033674bb88cf5960d67f101f0d5e00b376b9d54b5584e5592225812805ea56d70edfbc5ad189b54e2b0a9
6
+ metadata.gz: bcb368e9071a14ba6da85043a57d46897ab9ee016e9587ca674964c8a39858a6be0e476f47db94718db761efd4896e0b3db156eca1457ca6294e5b8fdb535b22
7
+ data.tar.gz: 1f5ca0c39dbd680fd81b6587fce069a05f5653f6b898e806a22a7ca3831572596f1218dff47bd898994492ae447b38e8fc58dcdb134b8c73e0d8b457948c2ed9
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
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
- # DataClassFactory
1
+ [![Gem Version](https://badge.fury.io/rb/data_class_factory.svg)](https://badge.fury.io/rb/data_class_factory)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/data_class_factory`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ # Data class factory
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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
- And then execute:
13
+ and then `bundle install`
16
14
 
17
- $ bundle
15
+ ## Usage
18
16
 
19
- Or install it yourself as:
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
- $ gem install data_class_factory
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
- ## Usage
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
- TODO: Write usage instructions here
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
- ## Development
41
+ # raises ArgumentError
42
+ # wrong number of arguments (given 2, expected 0)
43
+ p1 = Point.new(3, 4)
28
44
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
45
+ p1 = Point.new(x: 3, y: 4) # works well :)
46
+ ```
30
47
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
48
+ ### aliasing
32
49
 
33
- ## Contributing
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/data_class_factory. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
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](https://github.com/[USERNAME]/data_class_factory/blob/master/CODE_OF_CONDUCT.md).
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
- require "bundler/gem_tasks"
2
- require "rake/testtask"
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 << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
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 "bundler/setup"
4
- require "data_class_factory"
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 "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "data_class_factory/version"
5
+ require 'data_class_factory/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "data_class_factory"
8
+ spec.name = 'data_class_factory'
8
9
  spec.version = DataClassFactory::VERSION
9
- spec.authors = ["YusukeIwaki"]
10
+ spec.authors = ['YusukeIwaki']
10
11
 
11
- spec.summary = "Backport `Data.define` for Ruby < 3.2"
12
- spec.homepage = "https://github.com/YusukeIwaki/data_class_factory"
13
- spec.license = "MIT"
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 = "exe"
23
+ spec.bindir = 'exe'
23
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
- spec.require_paths = ["lib"]
25
+ spec.require_paths = ['lib']
25
26
 
26
27
  spec.required_ruby_version = '>= 2.7'
27
- spec.add_development_dependency "bundler"
28
- spec.add_development_dependency "rake"
29
- spec.add_development_dependency "minitest"
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0.0')
4
+ warn 'Removing original `Data` class', uplevel: 3
5
+ Object.send(:remove_const, :Data)
6
+ end
7
+ Data = DataClassFactory.define_factory_class if DataClassFactory.backport?
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DataClassFactory
2
- VERSION = "0.0.beta0"
4
+ VERSION = '0.1.0'
3
5
  end
@@ -1,6 +1,40 @@
1
- require "data_class_factory/version"
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
- class Error < StandardError; end
5
- # Your code goes here...
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.beta0
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-05 00:00:00.000000000 Z
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: minitest
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: 1.3.1
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 < 3.2
139
+ summary: Backport `Data.define` for Ruby 2.7, 3.0, 3.1
94
140
  test_files: []