attributes_dsl 0.0.2 → 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.
@@ -0,0 +1,141 @@
1
+ require "transproc/coercions"
2
+
3
+ module AttributesDSL
4
+ # The collection of pure composable functions
5
+ #
6
+ module Transprocs
7
+ extend Transproc::Registry
8
+
9
+ import :identity, from: Transproc::Coercions
10
+
11
+ # Symbolizes hash keys and filters them by given ones
12
+ #
13
+ # @example
14
+ # filter({ "foo" => :BAR, bar: :BAZ }, [:foo, :qux])
15
+ # # => { foo: :BAR }
16
+ #
17
+ # @param [Hash] attributes
18
+ # @param [Array] keys
19
+ #
20
+ # @return [Hash] attributes
21
+ #
22
+ def self.filter(attributes, keys)
23
+ attributes
24
+ .map { |key, value| [key.to_sym, value] }
25
+ .select { |key, _| keys.include? key }
26
+ .to_h
27
+ end
28
+
29
+ # Ensures all given keys are present in the hash
30
+ #
31
+ # @example
32
+ # filter({ foo: :BAR }, [:foo, :qux])
33
+ # # => { foo: :BAR, :qux: nil }
34
+ #
35
+ # @param [Hash] attributes
36
+ # @param [Array] keys
37
+ #
38
+ # @return [Hash]
39
+ #
40
+ def self.update(attributes, keys)
41
+ keys.map { |key| [key, attributes[key]] }.to_h
42
+ end
43
+
44
+ # Checks whether the name is present in attributes' keys,
45
+ # and transforms it using either the first or the second procedure
46
+ #
47
+ # @param [Hash] attributes
48
+ # @param [Symbol] name
49
+ # @param [Proc] presence
50
+ # @param [Proc] absence
51
+ #
52
+ # @return [Hash]
53
+ #
54
+ def self.convert(attributes, name, presence, absence)
55
+ if attributes.keys.include? name
56
+ presence[attributes]
57
+ else
58
+ absence[attributes]
59
+ end
60
+ end
61
+
62
+ # Complains about missed attribute in a hash
63
+ #
64
+ # @param [Hash] _attributes
65
+ # @param [#to_s] name
66
+ #
67
+ # @return [undefined]
68
+ # @raise [ArgumentError]
69
+ #
70
+ def self.missed(_attributes, name)
71
+ fail ArgumentError.new "Attribute '#{name}' is required"
72
+ end
73
+
74
+ # Updates the hash with default name's value
75
+ #
76
+ # @param [Hash] attributes
77
+ # @param [Symbol] name
78
+ # @param [Object] value
79
+ #
80
+ # @return [Hash]
81
+ #
82
+ def self.default(attributes, name, value)
83
+ attributes.merge(name => value)
84
+ end
85
+
86
+ # Checks if the attributes has no blacklisted values
87
+ #
88
+ # @param [Hash] attributes
89
+ # @param [Symbol] name
90
+ # @param [Array, Proc, Regexp, Range, Module] condition
91
+ #
92
+ # @return [Hash]
93
+ # @raise [ArgumentError] if the condition is satisfied
94
+ #
95
+ def self.blacklist(attributes, name, condition)
96
+ return attributes unless check(attributes[name], condition)
97
+
98
+ fail ArgumentError.new "Attribute #{name} is invalid: #{attributes[name]}"
99
+ end
100
+
101
+ # Checks if the attributes has whitelisted values
102
+ #
103
+ # @param [Hash] attributes
104
+ # @param [Symbol] name
105
+ # @param [Array, Proc, Regexp, Range, Module] condition
106
+ #
107
+ # @return [Hash]
108
+ # @raise [ArgumentError] if the condition is NOT satisfied
109
+ #
110
+ def self.whitelist(attributes, name, condition)
111
+ return attributes if check(attributes[name], condition)
112
+
113
+ fail ArgumentError.new "Attribute #{name} is invalid: #{attributes[name]}"
114
+ end
115
+
116
+ # Coerces attributes' name's value using given proc
117
+ #
118
+ # @example
119
+ # coerce({ foo: :BAR, bar: :BAZ }, :foo, -> v { v.to_s })
120
+ # # => { foo: "BAR", bar: :BAZ }
121
+ #
122
+ # @param [Hash] attributes
123
+ # @param [Symbol] name
124
+ # @param [Proc] coercer
125
+ #
126
+ # @return [Hash]
127
+ #
128
+ def self.coerce(attributes, name, coercer)
129
+ attributes.merge(name => coercer[attributes[name]])
130
+ end
131
+
132
+ private
133
+
134
+ def self.check(value, condition)
135
+ method = condition.is_a?(Array) ? :include? : :===
136
+ condition.send method, value
137
+ rescue
138
+ false
139
+ end
140
+ end
141
+ end
@@ -4,6 +4,6 @@ module AttributesDSL
4
4
 
5
5
  # The semantic version of the module.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.2".freeze
7
+ VERSION = "0.1.0".freeze
8
8
 
9
9
  end # module AttributesDSL
@@ -0,0 +1,49 @@
1
+ describe "blacklisted attribute" do
2
+ let(:substring) { Class.new(String) }
3
+ let(:klass) do
4
+ Class.new do
5
+ extend AttributesDSL
6
+
7
+ attribute :foo, except: /foo/
8
+ attribute :bar, except: 1..3
9
+ attribute :baz, except: %w(foo bar)
10
+ attribute :qux, except: -> v { v.even? }
11
+ attribute :yux, except: String
12
+ attribute :fix, except: String, coercer: -> v { v.to_s }
13
+ end
14
+ end
15
+
16
+ it "doesn't check unassigned values" do
17
+ expect { klass.new }.not_to raise_error
18
+ end
19
+
20
+ it "passes when all values are valid" do
21
+ params = { foo: "bar", bar: 0, baz: 0, qux: 3, yux: 1, fix: 2 }
22
+
23
+ expect { klass.new params }.not_to raise_error
24
+ end
25
+
26
+ it "checks regexp condition" do
27
+ expect { klass.new foo: "foo" }.to raise_error(ArgumentError, /foo/)
28
+ end
29
+
30
+ it "checks range condition" do
31
+ expect { klass.new bar: 2 }.to raise_error(ArgumentError, /bar/)
32
+ end
33
+
34
+ it "checks array condition" do
35
+ expect { klass.new baz: "foo" }.to raise_error(ArgumentError, /baz/)
36
+ end
37
+
38
+ it "checks proc condition" do
39
+ expect { klass.new qux: 4 }.to raise_error(ArgumentError, /qux/)
40
+ end
41
+
42
+ it "checks type" do
43
+ expect { klass.new yux: "foo" }.to raise_error(ArgumentError, /yux/)
44
+ end
45
+
46
+ it "applies validation before coersion" do
47
+ expect { klass.new fix: "foo" }.to raise_error(ArgumentError)
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ describe "coerced attribute" do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend AttributesDSL
5
+ attribute :foo, coercer: -> v { v.to_s }
6
+ attribute :bar, &:to_s
7
+ end
8
+ end
9
+
10
+ it "is not applied to skipped value" do
11
+ subject = klass.new
12
+
13
+ expect(subject.foo).to be_nil
14
+ end
15
+
16
+ it "is applied to assigned value" do
17
+ subject = klass.new foo: nil
18
+
19
+ expect(subject.foo).to eql("")
20
+ end
21
+
22
+ it "can be defined via block" do
23
+ subject = klass.new bar: :BAZ
24
+
25
+ expect(subject.bar).to eql("BAZ")
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ describe "required attribute" do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend AttributesDSL
5
+ attribute :foo, default: :FOO
6
+ end
7
+ end
8
+
9
+ it "adds default assignment" do
10
+ subject = klass.new
11
+
12
+ expect(subject.foo).to eql :FOO
13
+ end
14
+
15
+ it "assigns explicit value" do
16
+ subject = klass.new foo: :BAR
17
+
18
+ expect(subject.foo).to eql :BAR
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ describe "required attribute" do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend AttributesDSL
5
+ attribute :foo, required: true
6
+ attribute :bar
7
+ end
8
+ end
9
+
10
+ it "demands explicit assignment" do
11
+ expect { klass.new bar: :BAZ }.to raise_error(ArgumentError, /foo/)
12
+ end
13
+
14
+ it "allows nil values" do
15
+ expect { klass.new foo: nil }.not_to raise_error
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ describe "simple attribute" do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend AttributesDSL
5
+ attribute :foo
6
+ end
7
+ end
8
+
9
+ it "defines empty attributes" do
10
+ subject = klass.new
11
+
12
+ expect(subject.attributes).to eql(foo: nil)
13
+ expect(subject.foo).to be_nil
14
+ end
15
+
16
+ it "assigns attributes by symbolic keys" do
17
+ subject = klass.new foo: :BAR
18
+
19
+ expect(subject.attributes).to eql(foo: :BAR)
20
+ expect(subject.foo).to eql :BAR
21
+ end
22
+
23
+ it "assigns attributes by string keys" do
24
+ subject = klass.new "foo" => :BAR
25
+
26
+ expect(subject.attributes).to eql(foo: :BAR)
27
+ expect(subject.foo).to eql :BAR
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ describe "unknown attribute" do
2
+ let(:klass) { Class.new { extend AttributesDSL } }
3
+
4
+ subject { klass.new foo: :BAR }
5
+
6
+ it "is ignored" do
7
+ expect(subject).not_to respond_to :foo
8
+ expect(subject.attributes).to eql({})
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ describe "unreadable attribute" do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend AttributesDSL
5
+ attribute :foo, reader: false
6
+ end
7
+ end
8
+
9
+ subject { klass.new foo: :BAR }
10
+
11
+ it "defines the attribute" do
12
+ expect(subject.attributes).to eql(foo: :BAR)
13
+ end
14
+
15
+ it "doesn't define a reader" do
16
+ expect(subject).not_to respond_to :foo
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ describe "whitelisted attribute" do
2
+ let(:substring) { Class.new(String) }
3
+ let(:klass) do
4
+ Class.new do
5
+ extend AttributesDSL
6
+
7
+ attribute :foo, only: /foo/
8
+ attribute :bar, only: 1..3
9
+ attribute :baz, only: %w(foo bar)
10
+ attribute :qux, only: -> v { v.even? }
11
+ attribute :yux, only: String
12
+ attribute :fix, only: String, coercer: -> v { v.to_s }
13
+ end
14
+ end
15
+
16
+ it "doesn't check unassigned values" do
17
+ expect { klass.new }.not_to raise_error
18
+ end
19
+
20
+ it "passes when all values are valid" do
21
+ params = { foo: "foo", bar: 2, baz: "foo", qux: 2, yux: "1", fix: "2" }
22
+
23
+ expect { klass.new params }.not_to raise_error
24
+ end
25
+
26
+ it "checks regexp condition" do
27
+ expect { klass.new foo: "bar" }.to raise_error(ArgumentError, /foo/)
28
+ end
29
+
30
+ it "checks range condition" do
31
+ expect { klass.new bar: 4 }.to raise_error(ArgumentError, /bar/)
32
+ end
33
+
34
+ it "checks array condition" do
35
+ expect { klass.new baz: "qux" }.to raise_error(ArgumentError, /baz/)
36
+ end
37
+
38
+ it "checks proc condition" do
39
+ expect { klass.new qux: 3 }.to raise_error(ArgumentError, /qux/)
40
+ end
41
+
42
+ it "checks type" do
43
+ expect { klass.new yux: :foo }.to raise_error(ArgumentError, /yux/)
44
+ end
45
+
46
+ it "applies validation before coersion" do
47
+ expect { klass.new fix: :foo }.to raise_error(ArgumentError)
48
+ end
49
+ end
data/spec/spec_helper.rb CHANGED
@@ -10,3 +10,6 @@ end
10
10
 
11
11
  # Loads the code under test
12
12
  require "attributes_dsl"
13
+
14
+ # Loads specific matchers
15
+ require "ice_nine"
metadata CHANGED
@@ -1,57 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attributes_dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kozin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-11 00:00:00.000000000 Z
11
+ date: 2015-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ice_nine
14
+ name: equalizer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.11'
19
+ version: 0.0.11
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.11'
26
+ version: 0.0.11
27
27
  - !ruby/object:Gem::Dependency
28
- name: equalizer
28
+ name: transproc
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.0'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 0.0.11
33
+ version: 0.4.0
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - "~>"
42
39
  - !ruby/object:Gem::Version
43
- version: '0.0'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 0.0.11
40
+ version: 0.4.0
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: hexx-rspec
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
45
  - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '0.5'
54
- - - ">="
55
46
  - !ruby/object:Gem::Version
56
47
  version: 0.5.2
57
48
  type: :development
@@ -59,11 +50,22 @@ dependencies:
59
50
  version_requirements: !ruby/object:Gem::Requirement
60
51
  requirements:
61
52
  - - "~>"
62
- - !ruby/object:Gem::Version
63
- version: '0.5'
64
- - - ">="
65
53
  - !ruby/object:Gem::Version
66
54
  version: 0.5.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: ice_nine
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.11.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.11.1
67
69
  description:
68
70
  email: andrew.kozin@gmail.com
69
71
  executables: []
@@ -101,11 +103,17 @@ files:
101
103
  - lib/attributes_dsl.rb
102
104
  - lib/attributes_dsl/attribute.rb
103
105
  - lib/attributes_dsl/attributes.rb
106
+ - lib/attributes_dsl/transprocs.rb
104
107
  - lib/attributes_dsl/version.rb
105
- - spec/integration/attributes_dsl_spec.rb
108
+ - spec/integration/blacklisted_attribute_spec.rb
109
+ - spec/integration/coerced_attribute_spec.rb
110
+ - spec/integration/default_attribute_spec.rb
111
+ - spec/integration/required_attribute_spec.rb
112
+ - spec/integration/simple_attribute_spec.rb
113
+ - spec/integration/unknown_attribute_spec.rb
114
+ - spec/integration/unreadable_attribute_spec.rb
115
+ - spec/integration/whitelisted_attribute_spec.rb
106
116
  - spec/spec_helper.rb
107
- - spec/unit/attribute_spec.rb
108
- - spec/unit/attributes_spec.rb
109
117
  homepage: https://github.com/nepalez/attributes_dsl
110
118
  licenses:
111
119
  - MIT
@@ -118,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
126
  requirements:
119
127
  - - ">="
120
128
  - !ruby/object:Gem::Version
121
- version: 1.9.3
129
+ version: '2.1'
122
130
  required_rubygems_version: !ruby/object:Gem::Requirement
123
131
  requirements:
124
132
  - - ">="
@@ -132,7 +140,12 @@ specification_version: 4
132
140
  summary: Lightweight DSL to define PORO attributes
133
141
  test_files:
134
142
  - spec/spec_helper.rb
135
- - spec/integration/attributes_dsl_spec.rb
136
- - spec/unit/attribute_spec.rb
137
- - spec/unit/attributes_spec.rb
143
+ - spec/integration/required_attribute_spec.rb
144
+ - spec/integration/unknown_attribute_spec.rb
145
+ - spec/integration/simple_attribute_spec.rb
146
+ - spec/integration/default_attribute_spec.rb
147
+ - spec/integration/unreadable_attribute_spec.rb
148
+ - spec/integration/blacklisted_attribute_spec.rb
149
+ - spec/integration/coerced_attribute_spec.rb
150
+ - spec/integration/whitelisted_attribute_spec.rb
138
151
  has_rdoc: