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.
@@ -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,9 @@
1
+ # encoding: utf-8
2
+
3
+ module AttributesDSL
4
+
5
+ # The semantic version of the module.
6
+ # @see http://semver.org/ Semantic versioning 2.0
7
+ VERSION = "0.0.1".freeze
8
+
9
+ 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
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require "hexx-suit"
5
+ Hexx::Suit.load_metrics_for(self)
6
+ rescue LoadError
7
+ require "hexx-rspec"
8
+ Hexx::RSpec.load_metrics_for(self)
9
+ end
10
+
11
+ # Loads the code under test
12
+ require "attributes_dsl"
@@ -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: