value_class 0.1.0.pre.prerelease2
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/.gitignore +11 -0
- data/.jenkins/Jenkinsfile +61 -0
- data/.jenkins/ruby_build_pod.yml +19 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +69 -0
- data/README.md +100 -0
- data/Rakefile +19 -0
- data/lib/value_class/attribute.rb +95 -0
- data/lib/value_class/constructable.rb +103 -0
- data/lib/value_class/version.rb +5 -0
- data/lib/value_class.rb +115 -0
- data/value_class.gemspec +21 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3ea3aec695d36750ca2629b6348ae5ea195f7d0225b415051b2bf6df665faf5
|
4
|
+
data.tar.gz: d90ca345053f89ff2f3db5ea88ee5ea887aa4ca1633743cb09eff1e6c40391cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56047ca389a9e46e4d78498e1b65906363efb0ff20942389157504a7044ae9e508d7b9e74adcc57c533b15c524103502fb4b2e804d86301c7bcc794af97c0379
|
7
|
+
data.tar.gz: 3549fe478d4177a84cd2f97c5863f60e17b192a074acdc95c0f5c37f4acde03ffc320e4d7a8c3e385ac2e1811ab5bbe521c7618e5b2bb066324acd971e8f9b69
|
data/.gitignore
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/groovy
|
2
|
+
@Library('jenkins-pipeline@v0.4.5')
|
3
|
+
import com.invoca.docker.*;
|
4
|
+
|
5
|
+
pipeline {
|
6
|
+
agent {
|
7
|
+
kubernetes {
|
8
|
+
defaultContainer "ruby"
|
9
|
+
yamlFile ".jenkins/ruby_build_pod.yml"
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
environment {
|
14
|
+
GITHUB_TOKEN = credentials('github_token')
|
15
|
+
GITHUB_KEY = credentials('github_key')
|
16
|
+
BUNDLE_GEM__FURY__IO = credentials('gemfury_deploy_token')
|
17
|
+
}
|
18
|
+
|
19
|
+
stages {
|
20
|
+
stage('Setup') {
|
21
|
+
steps {
|
22
|
+
script {
|
23
|
+
sh '''
|
24
|
+
# get SSH setup inside the container
|
25
|
+
eval `ssh-agent -s`
|
26
|
+
echo "$GITHUB_KEY" | ssh-add -
|
27
|
+
mkdir -p /root/.ssh
|
28
|
+
ssh-keyscan -t rsa github.com > /root/.ssh/known_hosts
|
29
|
+
bundle install
|
30
|
+
'''
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
stage('Unit Test') {
|
36
|
+
steps {
|
37
|
+
script {
|
38
|
+
sh 'bundle exec rspec --format RspecJunitFormatter --out spec/reports/rspec.xml'
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
post {
|
43
|
+
always { junit 'spec/reports/rspec.xml' }
|
44
|
+
success { updateGitHubStatus('clean-build', 'success', 'Unit tests.') }
|
45
|
+
failure { updateGitHubStatus('clean-build', 'failure', 'Unit tests.') }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
void updateGitHubStatus(String context, String status, String description) {
|
52
|
+
gitHubStatus([
|
53
|
+
repoSlug: 'Invoca/value_class',
|
54
|
+
sha: env.GIT_COMMIT,
|
55
|
+
description: description,
|
56
|
+
context: context,
|
57
|
+
targetURL: env.BUILD_URL,
|
58
|
+
token: env.GITHUB_TOKEN,
|
59
|
+
status: status
|
60
|
+
])
|
61
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
apiVersion: v1
|
3
|
+
kind: Pod
|
4
|
+
metadata:
|
5
|
+
labels:
|
6
|
+
jenkins/active-table-set: 'true'
|
7
|
+
namespace: jenkins
|
8
|
+
name: active-table-set
|
9
|
+
spec:
|
10
|
+
containers:
|
11
|
+
- name: ruby
|
12
|
+
image: ruby:2.6.1
|
13
|
+
tty: true
|
14
|
+
resources:
|
15
|
+
requests:
|
16
|
+
memory: "100Mi"
|
17
|
+
command:
|
18
|
+
- cat
|
19
|
+
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
# This rubocop config file inherits from our centralized style-guide repository.
|
2
|
+
# If you are looking to update the ruby style guide, be sure to both update the rubocop config
|
3
|
+
# as well as the README.md
|
4
|
+
# https://github.com/Invoca/style-guide/tree/master/ruby
|
5
|
+
inherit_from: https://raw.githubusercontent.com/Invoca/style-guide/master/ruby/.rubocop.yml
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.1
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# CHANGELOG for `value_class`
|
2
|
+
|
3
|
+
Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
4
|
+
|
5
|
+
Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [0.1.0]
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
- Cloned from Active Table Set
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
source 'https://gem.fury.io/invoca'
|
3
|
+
|
4
|
+
# Specify your gem's dependencies in value_class.gemspec
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'bundler', '~> 1.8'
|
9
|
+
gem 'pry'
|
10
|
+
gem 'pry-byebug'
|
11
|
+
gem 'rake', '~> 13.0'
|
12
|
+
gem 'rspec', '~> 3.7'
|
13
|
+
gem 'rspec-mocks', '~> 3.7'
|
14
|
+
gem 'rspec_junit_formatter'
|
15
|
+
gem 'rubocop', '0.74.0'
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
value_class (0.1.0.pre.prerelease2)
|
5
|
+
attr_comparable
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
remote: https://gem.fury.io/invoca/
|
10
|
+
specs:
|
11
|
+
ast (2.4.0)
|
12
|
+
attr_comparable (0.2.0)
|
13
|
+
byebug (10.0.2)
|
14
|
+
coderay (1.1.2)
|
15
|
+
diff-lcs (1.3)
|
16
|
+
jaro_winkler (1.5.4)
|
17
|
+
method_source (0.9.0)
|
18
|
+
parallel (1.19.1)
|
19
|
+
parser (2.7.1.1)
|
20
|
+
ast (~> 2.4.0)
|
21
|
+
pry (0.11.3)
|
22
|
+
coderay (~> 1.1.0)
|
23
|
+
method_source (~> 0.9.0)
|
24
|
+
pry-byebug (3.6.0)
|
25
|
+
byebug (~> 10.0)
|
26
|
+
pry (~> 0.10)
|
27
|
+
rainbow (3.0.0)
|
28
|
+
rake (13.0.1)
|
29
|
+
rspec (3.7.0)
|
30
|
+
rspec-core (~> 3.7.0)
|
31
|
+
rspec-expectations (~> 3.7.0)
|
32
|
+
rspec-mocks (~> 3.7.0)
|
33
|
+
rspec-core (3.7.1)
|
34
|
+
rspec-support (~> 3.7.0)
|
35
|
+
rspec-expectations (3.7.0)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.7.0)
|
38
|
+
rspec-mocks (3.7.0)
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
+
rspec-support (~> 3.7.0)
|
41
|
+
rspec-support (3.7.1)
|
42
|
+
rspec_junit_formatter (0.4.1)
|
43
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
44
|
+
rubocop (0.74.0)
|
45
|
+
jaro_winkler (~> 1.5.1)
|
46
|
+
parallel (~> 1.10)
|
47
|
+
parser (>= 2.6)
|
48
|
+
rainbow (>= 2.2.2, < 4.0)
|
49
|
+
ruby-progressbar (~> 1.7)
|
50
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
51
|
+
ruby-progressbar (1.10.1)
|
52
|
+
unicode-display_width (1.6.1)
|
53
|
+
|
54
|
+
PLATFORMS
|
55
|
+
ruby
|
56
|
+
|
57
|
+
DEPENDENCIES
|
58
|
+
bundler (~> 1.8)
|
59
|
+
pry
|
60
|
+
pry-byebug
|
61
|
+
rake (~> 13.0)
|
62
|
+
rspec (~> 3.7)
|
63
|
+
rspec-mocks (~> 3.7)
|
64
|
+
rspec_junit_formatter
|
65
|
+
rubocop (= 0.74.0)
|
66
|
+
value_class!
|
67
|
+
|
68
|
+
BUNDLED WITH
|
69
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# ValueClass
|
2
|
+
|
3
|
+
Provides a simple mechanism for declaring a class with complex attributes. Instances of the class can be progressively
|
4
|
+
constructed in a using a block. The class is immutable outside of the block. This is useful for building simple
|
5
|
+
configuration DSLs.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'value_class'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install value_class
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
The following class declaration allow a bicycle to be declared:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class BikeTire
|
29
|
+
include ValueClass::Constructable
|
30
|
+
|
31
|
+
value_attr :diameter, description: "The diameter in inches"
|
32
|
+
value_attr :tred, description: "The tred on the tire"
|
33
|
+
end
|
34
|
+
|
35
|
+
class BikeSeat
|
36
|
+
include ValueClass::Constructable
|
37
|
+
|
38
|
+
value_attr :size, description: "The size of the bike seat in inches"
|
39
|
+
value_attr :color
|
40
|
+
end
|
41
|
+
|
42
|
+
class Bicycle
|
43
|
+
include ValueClass::Constructable
|
44
|
+
|
45
|
+
value_description "For riding around town"
|
46
|
+
value_attr :speeds
|
47
|
+
value_attr :color, default: :orange
|
48
|
+
value_attr :seat, class_name: 'BikeSeat'
|
49
|
+
|
50
|
+
value_list_attr :riders, insert_method: :add_rider
|
51
|
+
value_list_attr :tires, insert_method: :tire, class_name: 'BikeTire'
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Given the above declarations, you can then configure a bicycle with the following code.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
bike = Bicycle.config do |bicycle|
|
59
|
+
bicycle.speeds 10
|
60
|
+
bicycle.color :blue
|
61
|
+
bicycle.seat do |seat|
|
62
|
+
seat.color :green
|
63
|
+
seat.size :large
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
You can also directly declare the class:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
bike = Bicycle.new(speeds: 10, color: :gold, tires: [{ diameter: 40, tred: :mountain }, { diameter: 50, tred: :slicks }])
|
72
|
+
```
|
73
|
+
|
74
|
+
If you have a simple class, ValueClass provides a replacement for ruby struct that allows for a quick class declaration.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
|
78
|
+
Gears = ValueClass.struct(:first_gear, :second_gear, :third_gear, default: 200)
|
79
|
+
gear = Gears.new(first_gear: 20)
|
80
|
+
```
|
81
|
+
|
82
|
+
Once an instance of a class is returned. It is immutable: it is frozen and all if its attributes are frozen.
|
83
|
+
|
84
|
+
### Running Tests
|
85
|
+
|
86
|
+
Tests in this gem are written in Rspec and can be executed through the main rake task for the repo
|
87
|
+
```bash
|
88
|
+
bundle exec rake
|
89
|
+
```
|
90
|
+
|
91
|
+
If there is a subset of tests you would like to run, you can add the `focus: true` tag to the test or context to only run the subset of tests.
|
92
|
+
|
93
|
+
## Contributing
|
94
|
+
|
95
|
+
1. Fork it ( https://github.com/invoca/value_class/fork )
|
96
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
97
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
98
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
99
|
+
4. Make sure the tests pass: `rspec spec`
|
100
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
task :rubocop do
|
11
|
+
puts "rubocop"
|
12
|
+
rubocop_output = `rubocop -a app lib test`
|
13
|
+
print rubocop_output
|
14
|
+
unless rubocop_output.match(/files inspected, no offenses detected/)
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: [:spec, :rubocop]
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValueClass
|
4
|
+
class Attribute
|
5
|
+
attr_reader :name, :options, :limit
|
6
|
+
|
7
|
+
OPTIONS = {
|
8
|
+
description: "A description of the attribute.",
|
9
|
+
default: "The default value for this parameter.",
|
10
|
+
class_name: "The name of the value class for this attribute. Allows for construction from a nested hash.",
|
11
|
+
list_of_class: "Used to declare an attribute that is a list of a class.",
|
12
|
+
required: "If true, the parameter is required",
|
13
|
+
limit: "The set of valid values",
|
14
|
+
insert_method: "The name of the method to create to allow inserting into a list during progressive construction"
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(name, options)
|
18
|
+
if (invalid_options = options.keys - OPTIONS.keys).any?
|
19
|
+
raise ArgumentError, "Unknown option(s): #{invalid_options.join(',')}"
|
20
|
+
end
|
21
|
+
|
22
|
+
@name = name.to_sym.freeze
|
23
|
+
@options = options.freeze
|
24
|
+
@limit = options[:limit]
|
25
|
+
end
|
26
|
+
|
27
|
+
def description(prefix = "")
|
28
|
+
if options[:description]
|
29
|
+
"#{prefix}#{name}: #{options[:description]}"
|
30
|
+
else
|
31
|
+
"#{prefix}#{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_value(config)
|
36
|
+
raw_value = raw_value(config)
|
37
|
+
|
38
|
+
cast_value = cast_value(raw_value)
|
39
|
+
|
40
|
+
if cast_value.nil? && options[:required]
|
41
|
+
raise ArgumentError, "must provide a value for #{name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
if !cast_value.nil? && limit && !limit.include?(cast_value)
|
45
|
+
raise ArgumentError, "invalid value #{cast_value.inspect} for #{name}. allowed values #{limit.inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
if cast_value.nil?
|
49
|
+
default
|
50
|
+
else
|
51
|
+
cast_value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def hash_value(raw_value)
|
56
|
+
if options[:list_of_class] && raw_value.is_a?(Array)
|
57
|
+
raw_value.map(&:to_hash)
|
58
|
+
elsif options[:class_name] && raw_value
|
59
|
+
raw_value.to_hash
|
60
|
+
else
|
61
|
+
raw_value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def default
|
66
|
+
value = options[:default]
|
67
|
+
begin
|
68
|
+
value.dup
|
69
|
+
rescue TypeError
|
70
|
+
value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def cast_value(raw_value)
|
77
|
+
if options[:list_of_class] && raw_value.is_a?(Array)
|
78
|
+
inner_class = options[:list_of_class]
|
79
|
+
raw_value.map { |v| Object.const_get(inner_class).new(v).freeze }
|
80
|
+
elsif options[:class_name] && raw_value
|
81
|
+
Object.const_get(options[:class_name]).new(raw_value).freeze
|
82
|
+
else
|
83
|
+
raw_value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def raw_value(config)
|
88
|
+
if config.is_a?(Hash)
|
89
|
+
config[name]
|
90
|
+
else
|
91
|
+
config.send(name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValueClass
|
4
|
+
module Constructable
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.include(ValueClass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def clone_config
|
11
|
+
config = self.class.config_class.new
|
12
|
+
self.class.value_attributes.each do |attr|
|
13
|
+
current_value = send(attr.name)
|
14
|
+
dup_value =
|
15
|
+
begin
|
16
|
+
current_value.dup
|
17
|
+
rescue TypeError
|
18
|
+
current_value
|
19
|
+
end
|
20
|
+
config.send("#{attr.name}=", dup_value)
|
21
|
+
end
|
22
|
+
yield config
|
23
|
+
self.class.new(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
# Constructs an instance using the configuration created in the passed in block.
|
28
|
+
def config
|
29
|
+
config = config_class.new
|
30
|
+
value_attributes.each do |attr|
|
31
|
+
if attr.default
|
32
|
+
config.send("#{attr.name}=", attr.default)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
yield config
|
36
|
+
new(config)
|
37
|
+
end
|
38
|
+
|
39
|
+
def config_class
|
40
|
+
unless @config_class
|
41
|
+
@config_class = Class.new
|
42
|
+
|
43
|
+
value_attributes.each do |attribute|
|
44
|
+
# Define assignment operator
|
45
|
+
@config_class.send(:attr_writer, attribute.name)
|
46
|
+
|
47
|
+
define_accessor(attribute)
|
48
|
+
if (insert_method = attribute.options[:insert_method])
|
49
|
+
define_insert(attribute, insert_method)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@config_class
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def define_insert(attribute, insert_method)
|
59
|
+
if (class_name = attribute.options[:list_of_class])
|
60
|
+
config_class.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
|
61
|
+
def #{insert_method}(value=nil, &blk)
|
62
|
+
if blk
|
63
|
+
@#{attribute.name} << #{class_name}.config { |config| yield config }
|
64
|
+
else
|
65
|
+
@#{attribute.name} << #{class_name}.new(value)
|
66
|
+
end
|
67
|
+
@#{attribute.name}
|
68
|
+
end
|
69
|
+
EORUBY
|
70
|
+
else
|
71
|
+
config_class.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
|
72
|
+
def #{insert_method}(value)
|
73
|
+
@#{attribute.name} << value
|
74
|
+
@#{attribute.name}
|
75
|
+
end
|
76
|
+
EORUBY
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def define_accessor(attribute)
|
81
|
+
if (class_name = attribute.options[:class_name])
|
82
|
+
config_class.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
|
83
|
+
def #{attribute.name}(&blk)
|
84
|
+
if blk
|
85
|
+
@#{attribute.name} = #{class_name}.config { |config| yield config }
|
86
|
+
end
|
87
|
+
@#{attribute.name}
|
88
|
+
end
|
89
|
+
EORUBY
|
90
|
+
else
|
91
|
+
config_class.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
|
92
|
+
def #{attribute.name}(value = nil)
|
93
|
+
unless value.nil?
|
94
|
+
@#{attribute.name} = value
|
95
|
+
end
|
96
|
+
@#{attribute.name}
|
97
|
+
end
|
98
|
+
EORUBY
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/value_class.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'attr_comparable'
|
4
|
+
require 'value_class/attribute'
|
5
|
+
|
6
|
+
module ValueClass
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Default constructor
|
12
|
+
def initialize(config = {})
|
13
|
+
check_constructor_params(config)
|
14
|
+
self.class.declare_comparison_operators
|
15
|
+
self.class.value_attributes.each do |attribute|
|
16
|
+
instance_variable_set("@#{attribute.name}", attribute.get_value(config).freeze)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: These need to be added to attr_comparible
|
21
|
+
def eql?(other)
|
22
|
+
self == other
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash
|
26
|
+
self.class.value_attributes.map do |attribute|
|
27
|
+
instance_variable_get("@#{attribute.name}")
|
28
|
+
end.hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
self.class.value_attributes.each_with_object({}) do |attribute, hash|
|
33
|
+
# Attributes are frozen, but hash with indifferent access mutates values (!!!), so we have to dup
|
34
|
+
# in order to get a value we can use
|
35
|
+
unsafe_version = attribute.hash_value(instance_variable_get("@#{attribute.name}"))
|
36
|
+
safe_version =
|
37
|
+
begin
|
38
|
+
unsafe_version.dup
|
39
|
+
rescue TypeError
|
40
|
+
unsafe_version
|
41
|
+
end
|
42
|
+
|
43
|
+
hash[attribute.name.to_sym] = safe_version
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.struct(*args)
|
48
|
+
Class.new do
|
49
|
+
include ValueClass::Constructable
|
50
|
+
value_attrs(*args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def check_constructor_params(config)
|
57
|
+
if config.is_a?(Hash)
|
58
|
+
extra_keys = config.keys - self.class.value_attributes.map(&:name)
|
59
|
+
extra_keys.empty? or raise ArgumentError, "unknown attribute #{extra_keys.join(', ')}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
def value_description(value = nil)
|
65
|
+
if value
|
66
|
+
@value_description = value
|
67
|
+
end
|
68
|
+
@value_description
|
69
|
+
end
|
70
|
+
|
71
|
+
def value_list_attr(attribute_name, options = {})
|
72
|
+
value_attr(attribute_name, options.merge(default: [], class_name: nil, list_of_class: options[:class_name]))
|
73
|
+
end
|
74
|
+
|
75
|
+
def value_attr(attribute_name, options = {})
|
76
|
+
attribute = Attribute.new(attribute_name, options)
|
77
|
+
value_attributes << attribute
|
78
|
+
|
79
|
+
attr_reader(attribute.name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def value_attrs(*args)
|
83
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
84
|
+
args.each { |arg| value_attr(arg, options) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def config_help(prefix = "")
|
88
|
+
[
|
89
|
+
"#{name}: #{value_description}",
|
90
|
+
" attributes:",
|
91
|
+
value_attributes.map { |ca| ca.description(prefix + " ") }
|
92
|
+
].flatten.join("\n") + "\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
def value_attributes
|
96
|
+
@value_attributes ||= []
|
97
|
+
end
|
98
|
+
|
99
|
+
def declare_comparison_operators
|
100
|
+
unless @comparison_operators_declared
|
101
|
+
@comparison_operators_declared = true
|
102
|
+
|
103
|
+
include AttrComparable
|
104
|
+
attr_compare(value_attributes.map(&:name))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def inherited(new_child_class)
|
109
|
+
value_attributes.each { |attr| new_child_class.value_attributes << attr }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# These build off of the above, so they are required last
|
115
|
+
require 'value_class/constructable'
|
data/value_class.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'value_class/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "value_class"
|
7
|
+
spec.version = ValueClass::VERSION
|
8
|
+
spec.authors = ["Bob Smith"]
|
9
|
+
spec.email = ["bob@invoca.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{ValueClass a lightweight way to define configuration DSLs.}
|
12
|
+
spec.description = %q{ValueClass provides an interface to declare simple classes that can be progressively constructed but that are imutable afterwards.}
|
13
|
+
spec.homepage = "https://github.com/invoca/value_class"
|
14
|
+
spec.license = ""
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'attr_comparable'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: value_class
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.prerelease2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bob Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-04-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: attr_comparable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: ValueClass provides an interface to declare simple classes that can be
|
28
|
+
progressively constructed but that are imutable afterwards.
|
29
|
+
email:
|
30
|
+
- bob@invoca.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".gitignore"
|
36
|
+
- ".jenkins/Jenkinsfile"
|
37
|
+
- ".jenkins/ruby_build_pod.yml"
|
38
|
+
- ".rspec"
|
39
|
+
- ".rubocop.yml"
|
40
|
+
- ".ruby-version"
|
41
|
+
- ".travis.yml"
|
42
|
+
- CHANGELOG.md
|
43
|
+
- Gemfile
|
44
|
+
- Gemfile.lock
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- lib/value_class.rb
|
48
|
+
- lib/value_class/attribute.rb
|
49
|
+
- lib/value_class/constructable.rb
|
50
|
+
- lib/value_class/version.rb
|
51
|
+
- value_class.gemspec
|
52
|
+
homepage: https://github.com/invoca/value_class
|
53
|
+
licenses:
|
54
|
+
- ''
|
55
|
+
metadata: {}
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.3.1
|
70
|
+
requirements: []
|
71
|
+
rubygems_version: 3.0.1
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: ValueClass a lightweight way to define configuration DSLs.
|
75
|
+
test_files: []
|