attributes_dsl 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: