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.
- checksums.yaml +4 -4
- data/.travis.yml +0 -4
- data/CHANGELOG.md +23 -0
- data/README.md +29 -55
- data/attributes_dsl.gemspec +6 -7
- data/benchmark/run.rb +4 -4
- data/lib/attributes_dsl.rb +11 -24
- data/lib/attributes_dsl/attribute.rb +49 -34
- data/lib/attributes_dsl/attributes.rb +34 -34
- data/lib/attributes_dsl/transprocs.rb +141 -0
- data/lib/attributes_dsl/version.rb +1 -1
- data/spec/integration/blacklisted_attribute_spec.rb +49 -0
- data/spec/integration/coerced_attribute_spec.rb +27 -0
- data/spec/integration/default_attribute_spec.rb +20 -0
- data/spec/integration/required_attribute_spec.rb +17 -0
- data/spec/integration/simple_attribute_spec.rb +29 -0
- data/spec/integration/unknown_attribute_spec.rb +10 -0
- data/spec/integration/unreadable_attribute_spec.rb +18 -0
- data/spec/integration/whitelisted_attribute_spec.rb +49 -0
- data/spec/spec_helper.rb +3 -0
- metadata +40 -27
- data/spec/integration/attributes_dsl_spec.rb +0 -76
- data/spec/unit/attribute_spec.rb +0 -126
- data/spec/unit/attributes_spec.rb +0 -82
@@ -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
|
@@ -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,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
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
|
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-
|
11
|
+
date: 2015-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: equalizer
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
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:
|
26
|
+
version: 0.0.11
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: transproc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
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:
|
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/
|
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
|
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/
|
136
|
-
- spec/
|
137
|
-
- spec/
|
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:
|