attributes_dsl 0.0.1
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/.coveralls.yml +2 -0
- data/.gitignore +9 -0
- data/.metrics +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +20 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +16 -0
- data/Guardfile +14 -0
- data/LICENSE +21 -0
- data/README.md +209 -0
- data/Rakefile +39 -0
- data/attributes_dsl.gemspec +26 -0
- data/benchmark/run.rb +122 -0
- data/config/metrics/STYLEGUIDE +219 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +14 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +72 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/attributes_dsl.rb +98 -0
- data/lib/attributes_dsl/attribute.rb +69 -0
- data/lib/attributes_dsl/attributes.rb +73 -0
- data/lib/attributes_dsl/version.rb +9 -0
- data/spec/integration/attributes_dsl_spec.rb +51 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/unit/attribute_spec.rb +126 -0
- data/spec/unit/attributes_spec.rb +76 -0
- metadata +138 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AttributesDSL
|
4
|
+
|
5
|
+
# Describes settings for PORO attribute
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @author Andrew Kozin <Andrew.Kozin@gmail.com>
|
10
|
+
#
|
11
|
+
class Attribute
|
12
|
+
|
13
|
+
include Equalizer.new(:name)
|
14
|
+
|
15
|
+
# @!attribute [r] name
|
16
|
+
#
|
17
|
+
# @return [Symbol] the name of the attribute
|
18
|
+
#
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# @!attribute [r] default
|
22
|
+
#
|
23
|
+
# @return [Object] the default value of the attribute
|
24
|
+
#
|
25
|
+
attr_reader :default
|
26
|
+
|
27
|
+
# @!attribute [r] required
|
28
|
+
#
|
29
|
+
# @return [Boolean] whether the attribute is required
|
30
|
+
#
|
31
|
+
attr_reader :required
|
32
|
+
|
33
|
+
# @!attribute [r] coercer
|
34
|
+
#
|
35
|
+
# @return [Proc, nil] the coercer for the attribute
|
36
|
+
#
|
37
|
+
attr_reader :coercer
|
38
|
+
|
39
|
+
# Initializes the attribute
|
40
|
+
#
|
41
|
+
# @param [Symbol] name
|
42
|
+
# @param [Hash] options
|
43
|
+
# @param [Proc] coercer
|
44
|
+
#
|
45
|
+
# @option options [Object] :default
|
46
|
+
# @option options [Boolean] :required
|
47
|
+
#
|
48
|
+
def initialize(name, options = {}, &coercer)
|
49
|
+
@name = name
|
50
|
+
@default = options.fetch(:default) {}
|
51
|
+
@required = default.nil? && options.fetch(:required) { false }
|
52
|
+
@coercer = coercer
|
53
|
+
|
54
|
+
IceNine.deep_freeze(self)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Coerces an input assigned to the attribute
|
58
|
+
#
|
59
|
+
# @param [Object] input
|
60
|
+
#
|
61
|
+
# @return [Object]
|
62
|
+
#
|
63
|
+
def value(input)
|
64
|
+
coercer ? coercer[input] : input
|
65
|
+
end
|
66
|
+
|
67
|
+
end # class Attribute
|
68
|
+
|
69
|
+
end # module AttributesDSL
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AttributesDSL
|
4
|
+
|
5
|
+
# Describes a collection of attributes declaration with methods
|
6
|
+
# to validate and extract instance attributes from a hash.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
#
|
10
|
+
# @author Andrew Kozin <Andrew.Kozin@gmail.com>
|
11
|
+
#
|
12
|
+
class Attributes
|
13
|
+
|
14
|
+
# @!attribute [r] attributes
|
15
|
+
#
|
16
|
+
# Uses the set of attributes to ensure their uniqueness (by name)
|
17
|
+
#
|
18
|
+
# @return [Set] the set of registered attributes
|
19
|
+
#
|
20
|
+
attr_reader :attributes
|
21
|
+
|
22
|
+
# Initializes an immutable collection with an initial set of attributes
|
23
|
+
#
|
24
|
+
# @param [Set] attributes
|
25
|
+
#
|
26
|
+
def initialize(attributes = nil)
|
27
|
+
@attributes = Set.new attributes
|
28
|
+
IceNine.deep_freeze(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initializes the attribute from given arguments
|
32
|
+
# and returns new immutable collection with the attribute
|
33
|
+
#
|
34
|
+
# @param (see Attribute#initialize)
|
35
|
+
#
|
36
|
+
# @return [AttributesDSL::Attributes]
|
37
|
+
#
|
38
|
+
def register(name, options = {}, &coercer)
|
39
|
+
self.class.new(attributes.to_a << Attribute.new(name, options, &coercer))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extracts instance attributes from the input hash
|
43
|
+
#
|
44
|
+
# Assigns default values and uses coercions when applicable.
|
45
|
+
#
|
46
|
+
# @param [Hash] input
|
47
|
+
#
|
48
|
+
# @return [Hash]
|
49
|
+
#
|
50
|
+
def extract(input)
|
51
|
+
validate(input).inject({}) do |a, e|
|
52
|
+
key = e.name
|
53
|
+
value = input.fetch(key) { e.default }
|
54
|
+
a.merge(key => e.value(value))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def validate(input)
|
61
|
+
undefined = required - input.keys
|
62
|
+
return attributes if undefined.empty?
|
63
|
+
|
64
|
+
fail ArgumentError.new "Undefined attributes: #{undefined.join(", ")}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def required
|
68
|
+
attributes.select(&:required).map(&:name)
|
69
|
+
end
|
70
|
+
|
71
|
+
end # class Attributes
|
72
|
+
|
73
|
+
end # module AttributesDSL
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe AttributesDSL do
|
4
|
+
|
5
|
+
let(:coercer) { -> value { value.to_s } }
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
extend AttributesDSL
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
super
|
12
|
+
IceNine.deep_freeze(self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
klass.attribute(:foo, default: :FOO, &:to_s)
|
19
|
+
klass.attribute("bar", required: true)
|
20
|
+
klass.attribute(:baz, &:to_i)
|
21
|
+
end
|
22
|
+
|
23
|
+
subject { klass.new(arguments) }
|
24
|
+
|
25
|
+
context "when all required attributes are set" do
|
26
|
+
let(:arguments) { { bar: :BAR, baz: "42" } }
|
27
|
+
|
28
|
+
it "sets the attributes" do
|
29
|
+
expect(subject.attributes).to eql(foo: "FOO", bar: :BAR, baz: 42)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "defines methods for every attribute" do
|
33
|
+
expect(subject.foo).to eql "FOO"
|
34
|
+
expect(subject.bar).to eql :BAR
|
35
|
+
expect(subject.baz).to eql 42
|
36
|
+
end
|
37
|
+
|
38
|
+
it "doesn't freeze argument" do
|
39
|
+
expect { subject }.not_to change { arguments.frozen? }
|
40
|
+
end
|
41
|
+
end # context
|
42
|
+
|
43
|
+
context "when a required attribute is missed" do
|
44
|
+
let(:arguments) { { foo: :FOO, baz: "42" } }
|
45
|
+
|
46
|
+
it "fails" do
|
47
|
+
expect { subject }.to raise_error ArgumentError
|
48
|
+
end
|
49
|
+
end # context
|
50
|
+
|
51
|
+
end # describe AttributesDSL
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe AttributesDSL::Attribute do
|
4
|
+
|
5
|
+
let(:attribute) do
|
6
|
+
described_class.new(name, default: default, required: required, &coercer)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:name) { :foo }
|
10
|
+
let(:default) { "FOO" }
|
11
|
+
let(:required) { true }
|
12
|
+
let(:coercer) { -> v { v.to_s } }
|
13
|
+
|
14
|
+
describe ".new" do
|
15
|
+
subject { attribute }
|
16
|
+
|
17
|
+
it { is_expected.to be_frozen }
|
18
|
+
end # describe .new
|
19
|
+
|
20
|
+
describe "#name" do
|
21
|
+
subject { attribute.name }
|
22
|
+
|
23
|
+
it { is_expected.to eql name.to_sym }
|
24
|
+
end # describe #name
|
25
|
+
|
26
|
+
describe "#default" do
|
27
|
+
subject { attribute.default }
|
28
|
+
|
29
|
+
context "when value is given" do
|
30
|
+
it { is_expected.to eql default }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when value isn't set" do
|
34
|
+
let(:attribute) { described_class.new name }
|
35
|
+
|
36
|
+
it { is_expected.to be_nil }
|
37
|
+
end
|
38
|
+
end # describe #default
|
39
|
+
|
40
|
+
describe "#required" do
|
41
|
+
subject { attribute.required }
|
42
|
+
|
43
|
+
context "when set to true without default value" do
|
44
|
+
let(:attribute) { described_class.new name, required: true }
|
45
|
+
|
46
|
+
it { is_expected.to eql true }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when set to true with default value" do
|
50
|
+
let(:attribute) { described_class.new name, required: true, default: 1 }
|
51
|
+
|
52
|
+
it { is_expected.to eql false }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when value isn't set" do
|
56
|
+
let(:attribute) { described_class.new name }
|
57
|
+
|
58
|
+
it { is_expected.to eql false }
|
59
|
+
end
|
60
|
+
end # describe #required
|
61
|
+
|
62
|
+
describe "#coercer" do
|
63
|
+
subject { attribute.coercer }
|
64
|
+
|
65
|
+
context "when block is given" do
|
66
|
+
it { is_expected.to eql coercer }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when block isn't given" do
|
70
|
+
let(:attribute) { described_class.new name }
|
71
|
+
|
72
|
+
it { is_expected.to be_nil }
|
73
|
+
end
|
74
|
+
end # describe #coercer
|
75
|
+
|
76
|
+
describe "#value" do
|
77
|
+
subject { attribute.value(1) }
|
78
|
+
|
79
|
+
context "when coercer is given" do
|
80
|
+
it "uses the coercer" do
|
81
|
+
expect(subject).to eql "1"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when coercer isn't given" do
|
86
|
+
let(:attribute) { described_class.new name }
|
87
|
+
|
88
|
+
it "returns the input" do
|
89
|
+
expect(subject).to eql 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end # describe #value
|
93
|
+
|
94
|
+
describe "#==" do
|
95
|
+
subject { attribute == other }
|
96
|
+
|
97
|
+
context "with the same name" do
|
98
|
+
let(:other) { described_class.new name }
|
99
|
+
|
100
|
+
it { is_expected.to eql true }
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with another name" do
|
104
|
+
let(:other) { described_class.new :bar }
|
105
|
+
|
106
|
+
it { is_expected.to eql false }
|
107
|
+
end
|
108
|
+
end # describe #==
|
109
|
+
|
110
|
+
describe "#eql?" do
|
111
|
+
subject { attribute.eql? other }
|
112
|
+
|
113
|
+
context "with the same name" do
|
114
|
+
let(:other) { described_class.new name }
|
115
|
+
|
116
|
+
it { is_expected.to eql true }
|
117
|
+
end
|
118
|
+
|
119
|
+
context "with another name" do
|
120
|
+
let(:other) { described_class.new :bar }
|
121
|
+
|
122
|
+
it { is_expected.to eql false }
|
123
|
+
end
|
124
|
+
end # describe #eql?
|
125
|
+
|
126
|
+
end # describe AttributesDSL::Attribute
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe AttributesDSL::Attributes do
|
4
|
+
|
5
|
+
let(:attributes) { described_class.new }
|
6
|
+
let(:coercer) { -> value { value.to_s } }
|
7
|
+
|
8
|
+
describe ".new" do
|
9
|
+
subject { attributes }
|
10
|
+
|
11
|
+
it { is_expected.to be_frozen }
|
12
|
+
end # describe .new
|
13
|
+
|
14
|
+
describe "#attributes" do
|
15
|
+
subject { attributes.attributes }
|
16
|
+
|
17
|
+
it { is_expected.to be_kind_of Set }
|
18
|
+
it { is_expected.to be_empty }
|
19
|
+
end # describe #attributes
|
20
|
+
|
21
|
+
describe "#register" do
|
22
|
+
subject { attributes.register(:foo, default: :FOO, &coercer) }
|
23
|
+
|
24
|
+
it "returns new collection" do
|
25
|
+
expect(subject).to be_kind_of described_class
|
26
|
+
end
|
27
|
+
|
28
|
+
it "registers the attribute" do
|
29
|
+
attribute = subject.attributes.first
|
30
|
+
|
31
|
+
expect(attribute).to be_kind_of AttributesDSL::Attribute
|
32
|
+
expect(attribute.name).to eql :foo
|
33
|
+
expect(attribute.default).to eql :FOO
|
34
|
+
expect(attribute.coercer).to eql coercer
|
35
|
+
end
|
36
|
+
|
37
|
+
context "without options" do
|
38
|
+
subject { attributes.register :foo }
|
39
|
+
|
40
|
+
it { is_expected.to be_kind_of described_class }
|
41
|
+
end
|
42
|
+
end # describe #register
|
43
|
+
|
44
|
+
describe "#extract" do
|
45
|
+
subject { attributes.extract hash }
|
46
|
+
|
47
|
+
let(:hash) { { bar: :BAR } }
|
48
|
+
let(:attributes) do
|
49
|
+
described_class
|
50
|
+
.new
|
51
|
+
.register(:foo, required: true, &coercer)
|
52
|
+
.register(:bar, required: true)
|
53
|
+
.register(:baz, default: :BAZ, &coercer)
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when all required attributes are present" do
|
57
|
+
let(:hash) { { foo: :FOO, bar: :BAR, qux: :QUX } }
|
58
|
+
|
59
|
+
it "returns a proper hash" do
|
60
|
+
expect(subject).to eql(foo: "FOO", bar: :BAR, baz: "BAZ")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when a required attribute is absent" do
|
65
|
+
let(:hash) { { baz: :BAZ } }
|
66
|
+
|
67
|
+
it "fails" do
|
68
|
+
expect { subject }.to raise_error do |error|
|
69
|
+
expect(error).to be_kind_of ArgumentError
|
70
|
+
expect(error.message).to eql "Undefined attributes: foo, bar"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end # describe #extract
|
75
|
+
|
76
|
+
end # describe AttributeDSL::Attributes
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attributes_dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Kozin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ice_nine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.11'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: equalizer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.0.11
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.0.11
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: hexx-rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.5'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.5.2
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.5'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.5.2
|
67
|
+
description:
|
68
|
+
email: andrew.kozin@gmail.com
|
69
|
+
executables: []
|
70
|
+
extensions: []
|
71
|
+
extra_rdoc_files:
|
72
|
+
- README.md
|
73
|
+
- LICENSE
|
74
|
+
files:
|
75
|
+
- ".coveralls.yml"
|
76
|
+
- ".gitignore"
|
77
|
+
- ".metrics"
|
78
|
+
- ".rspec"
|
79
|
+
- ".rubocop.yml"
|
80
|
+
- ".travis.yml"
|
81
|
+
- ".yardopts"
|
82
|
+
- CHANGELOG.md
|
83
|
+
- Gemfile
|
84
|
+
- Guardfile
|
85
|
+
- LICENSE
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- attributes_dsl.gemspec
|
89
|
+
- benchmark/run.rb
|
90
|
+
- config/metrics/STYLEGUIDE
|
91
|
+
- config/metrics/cane.yml
|
92
|
+
- config/metrics/churn.yml
|
93
|
+
- config/metrics/flay.yml
|
94
|
+
- config/metrics/metric_fu.yml
|
95
|
+
- config/metrics/reek.yml
|
96
|
+
- config/metrics/roodi.yml
|
97
|
+
- config/metrics/rubocop.yml
|
98
|
+
- config/metrics/saikuro.yml
|
99
|
+
- config/metrics/simplecov.yml
|
100
|
+
- config/metrics/yardstick.yml
|
101
|
+
- lib/attributes_dsl.rb
|
102
|
+
- lib/attributes_dsl/attribute.rb
|
103
|
+
- lib/attributes_dsl/attributes.rb
|
104
|
+
- lib/attributes_dsl/version.rb
|
105
|
+
- spec/integration/attributes_dsl_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
- spec/unit/attribute_spec.rb
|
108
|
+
- spec/unit/attributes_spec.rb
|
109
|
+
homepage: https://github.com/nepalez/attributes_dsl
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 1.9.3
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.4.8
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Lightweight DSL to define PORO attributes
|
133
|
+
test_files:
|
134
|
+
- spec/spec_helper.rb
|
135
|
+
- spec/integration/attributes_dsl_spec.rb
|
136
|
+
- spec/unit/attribute_spec.rb
|
137
|
+
- spec/unit/attributes_spec.rb
|
138
|
+
has_rdoc:
|