attributes_dsl 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +52 -14
- data/benchmark/run.rb +12 -12
- data/lib/attributes_dsl.rb +9 -16
- data/lib/attributes_dsl/attributes.rb +8 -6
- data/lib/attributes_dsl/version.rb +1 -1
- data/spec/integration/attributes_dsl_spec.rb +53 -28
- data/spec/unit/attributes_spec.rb +10 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0a8b2957544474ff4116fd2b6d1703de977ba74
|
4
|
+
data.tar.gz: b61d244f9ea674fdff84404e269dfa083fca6f64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de35390efe7b4feb77a049d040884615e11c5de61e9d8cb3bb595fbf2b71a0b0e3d53b5dd78425c6e775774366bb39cd538b5b6fa3e1af6c67e61ec03863669d
|
7
|
+
data.tar.gz: 213cac8c92f35d396ce1272b53baace3a03ee635521e573cf039c85fd72bf4f43f384f46ae7d958188f9ca1d6c3424f82bc992e82c94b7fecd5221c6f10fbff4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
## version 0.0.2 2015-09-11
|
2
|
+
|
3
|
+
This version is a result of applying v0.0.1 to the existing gems:
|
4
|
+
|
5
|
+
* [abstract_mapper](https://github.com/nepalez/abstract_mapper)
|
6
|
+
* [query_builder](https://github.com/nepalez/query_builder)
|
7
|
+
* [rom-kafka](https://github.com/nepalez/rom-kafka) (WIP)
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
* Support for inheritance of attributes (nepalez)
|
12
|
+
|
13
|
+
### Bugs fixed
|
14
|
+
|
15
|
+
* Allow initializer to be used without arguments (nepalez)
|
16
|
+
* Allow attributes to be redefined (nepalez)
|
17
|
+
* Remove `AttributesDSL#new` and move its logics to the initializer (nepalez)
|
18
|
+
|
19
|
+
[Compare v0.0.1...v0.0.2](https://github.com/nepalez/attributes_dsl/compare/v0.0.1...v0.0.2)
|
20
|
+
|
1
21
|
## version 0.0.1 2015-09-10
|
2
22
|
|
3
23
|
This is the first published version
|
data/README.md
CHANGED
@@ -60,13 +60,18 @@ user = User.new(sex: :women, age: "26", place: "Moscow")
|
|
60
60
|
Additional Details
|
61
61
|
------------------
|
62
62
|
|
63
|
+
### Attribute declaration
|
64
|
+
|
63
65
|
The `attribute` class method takes the `name` and 2 options:
|
66
|
+
|
64
67
|
- `:default` for the default value (otherwise `nil`);
|
65
68
|
- `:required` to declare the attribute as required. It will be ignored if a default value is provided!
|
66
69
|
|
67
70
|
It is also takes the block, used to coerce a value. The coercer is applied to the default value too.
|
68
71
|
|
69
|
-
|
72
|
+
### Instance methods
|
73
|
+
|
74
|
+
Instance methods (like `#name`) are just aliases for the corresponding value of the `#attributes` hash. Instance variables aren't defined for them (to ensure syncronization between `#name` and `#attributes[:name]`):
|
70
75
|
|
71
76
|
```ruby
|
72
77
|
user = User.new(name: "John")
|
@@ -77,6 +82,39 @@ user.name # => :John
|
|
77
82
|
user.instance_variable_get :@name # => nil
|
78
83
|
```
|
79
84
|
|
85
|
+
### Inheritance
|
86
|
+
|
87
|
+
Subclasses inherits attributes of the superclass:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class UserWithRole < User
|
91
|
+
attribute :role, default: :user
|
92
|
+
end
|
93
|
+
|
94
|
+
user = UserWithRole.new(name: "Sam")
|
95
|
+
user.attributes
|
96
|
+
# => { name: :Sam, sex: :male, age: 0, position: nil, role: :user }
|
97
|
+
```
|
98
|
+
|
99
|
+
### Undefining Attributes
|
100
|
+
|
101
|
+
This feature is not available (and it won't be).
|
102
|
+
|
103
|
+
The reason is that a subclass should satisfy a contract of its superclass, including the existence of attributes, declared by the superclass.
|
104
|
+
All you can do is reload attribute definition in a subclass:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class Person < User
|
108
|
+
attribute :name, &:to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
user = Person.new(name: :Sam)
|
112
|
+
user.attributes
|
113
|
+
# => { name: "Sam", sex: :male, age: 0, position: nil }
|
114
|
+
```
|
115
|
+
|
116
|
+
### Freezing
|
117
|
+
|
80
118
|
You're free to redefine attributes (class settings are used by the initializer only):
|
81
119
|
|
82
120
|
```ruby
|
@@ -122,20 +160,20 @@ The results are following:
|
|
122
160
|
|
123
161
|
```
|
124
162
|
-------------------------------------------------
|
125
|
-
anima
|
126
|
-
kwattr
|
127
|
-
fast_attributes
|
128
|
-
attributes_dsl
|
129
|
-
active_attr
|
130
|
-
virtus 45.
|
163
|
+
anima 211.638k (± 3.7%) i/s - 1.071M
|
164
|
+
kwattr 187.276k (± 3.6%) i/s - 947.484k
|
165
|
+
fast_attributes 160.916k (± 2.4%) i/s - 816.726k
|
166
|
+
attributes_dsl 71.850k (± 3.0%) i/s - 365.365k
|
167
|
+
active_attr 71.489k (± 3.6%) i/s - 357.995k
|
168
|
+
virtus 45.554k (± 7.1%) i/s - 229.338k
|
131
169
|
|
132
170
|
Comparison:
|
133
|
-
anima:
|
134
|
-
kwattr:
|
135
|
-
fast_attributes:
|
136
|
-
attributes_dsl:
|
137
|
-
active_attr:
|
138
|
-
virtus:
|
171
|
+
anima: 211637.9 i/s
|
172
|
+
kwattr: 187276.2 i/s - 1.13x slower
|
173
|
+
fast_attributes: 160916.1 i/s - 1.32x slower
|
174
|
+
attributes_dsl: 71850.0 i/s - 2.95x slower
|
175
|
+
active_attr: 71489.1 i/s - 2.96x slower
|
176
|
+
virtus: 45553.8 i/s - 4.65x slower
|
139
177
|
```
|
140
178
|
|
141
179
|
Results above are pretty reasonable.
|
@@ -144,7 +182,7 @@ The gem is faster than `virtus` that has many additional features.
|
|
144
182
|
|
145
183
|
It is as fast as `active_attrs` (but has more customizable coercers).
|
146
184
|
|
147
|
-
It is 2 times slower than `fast_attributes` that has no coercer and default values. And it is 3
|
185
|
+
It is 2 times slower than `fast_attributes` that has no coercer and default values. And it is 3 times slower than `anima` and `kwattr` that provides only the base settings.
|
148
186
|
|
149
187
|
Installation
|
150
188
|
------------
|
data/benchmark/run.rb
CHANGED
@@ -94,28 +94,28 @@ end
|
|
94
94
|
Benchmark.ips do |x|
|
95
95
|
x.config time: 5, warmup: 5
|
96
96
|
|
97
|
-
x.report("
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
x.report("virtus") do
|
102
|
-
VirtusExample.call
|
97
|
+
x.report("anima") do
|
98
|
+
AnimaExample.call
|
103
99
|
end
|
104
100
|
|
105
|
-
x.report("
|
106
|
-
|
101
|
+
x.report("kwattr") do
|
102
|
+
KwattrExample.call
|
107
103
|
end
|
108
104
|
|
109
105
|
x.report("fast_attributes") do
|
110
106
|
FastAttributesExample.call
|
111
107
|
end
|
112
108
|
|
113
|
-
x.report("
|
114
|
-
|
109
|
+
x.report("attributes_dsl") do
|
110
|
+
AttributesDSLExample.call
|
115
111
|
end
|
116
112
|
|
117
|
-
x.report("
|
118
|
-
|
113
|
+
x.report("active_attr") do
|
114
|
+
AttributesDSLExample.call
|
115
|
+
end
|
116
|
+
|
117
|
+
x.report("virtus") do
|
118
|
+
VirtusExample.call
|
119
119
|
end
|
120
120
|
|
121
121
|
x.compare!
|
data/lib/attributes_dsl.rb
CHANGED
@@ -56,24 +56,17 @@ module AttributesDSL
|
|
56
56
|
define_method(s_name) { attributes.fetch(s_name) }
|
57
57
|
end
|
58
58
|
|
59
|
-
# Object contstructor that filters hash of attributes
|
60
|
-
#
|
61
|
-
# @param [Hash] hash The hash of attributes to be assinged
|
62
|
-
#
|
63
|
-
# @return [Object]
|
64
|
-
#
|
65
|
-
# @raise [ArgumentError] in case a required attribute is missed
|
66
|
-
#
|
67
|
-
def new(hash)
|
68
|
-
super attributes.extract(hash)
|
69
|
-
end
|
70
|
-
|
71
59
|
# @private
|
72
60
|
def self.extended(klass)
|
73
61
|
# use __send__ for compatibility to 1.9.3 (where `.include` was private)
|
74
62
|
klass.__send__(:include, InstanceMethods)
|
75
63
|
end
|
76
64
|
|
65
|
+
# @private
|
66
|
+
def inherited(klass)
|
67
|
+
klass.instance_variable_set(:@attributes, attributes)
|
68
|
+
end
|
69
|
+
|
77
70
|
# Defines instance methods for the hash of attributes and its initializer
|
78
71
|
module InstanceMethods
|
79
72
|
|
@@ -85,12 +78,12 @@ module AttributesDSL
|
|
85
78
|
|
86
79
|
# Initializes the object and sets the hash of its [#attributes]
|
87
80
|
#
|
88
|
-
# Uses attributes prepared by [.new]
|
89
|
-
#
|
90
81
|
# @param [Hash] attributes
|
91
82
|
#
|
92
|
-
|
93
|
-
|
83
|
+
# @raise [ArgumentError] in case a required attribute is missed
|
84
|
+
#
|
85
|
+
def initialize(attributes = {})
|
86
|
+
@attributes = self.class.attributes.extract(attributes)
|
94
87
|
end
|
95
88
|
|
96
89
|
end # module InstanceMethods
|
@@ -21,10 +21,10 @@ module AttributesDSL
|
|
21
21
|
|
22
22
|
# Initializes an immutable collection with an initial set of attributes
|
23
23
|
#
|
24
|
-
# @param [
|
24
|
+
# @param [Hash] attributes
|
25
25
|
#
|
26
|
-
def initialize(attributes =
|
27
|
-
@attributes =
|
26
|
+
def initialize(attributes = {})
|
27
|
+
@attributes = attributes
|
28
28
|
IceNine.deep_freeze(self)
|
29
29
|
end
|
30
30
|
|
@@ -36,7 +36,9 @@ module AttributesDSL
|
|
36
36
|
# @return [AttributesDSL::Attributes]
|
37
37
|
#
|
38
38
|
def register(name, options = {}, &coercer)
|
39
|
-
self.class.new(
|
39
|
+
self.class.new(
|
40
|
+
attributes.merge(name => Attribute.new(name, options, &coercer))
|
41
|
+
)
|
40
42
|
end
|
41
43
|
|
42
44
|
# Extracts instance attributes from the input hash
|
@@ -59,13 +61,13 @@ module AttributesDSL
|
|
59
61
|
|
60
62
|
def validate(input)
|
61
63
|
undefined = required - input.keys
|
62
|
-
return attributes if undefined.empty?
|
64
|
+
return attributes.values if undefined.empty?
|
63
65
|
|
64
66
|
fail ArgumentError.new "Undefined attributes: #{undefined.join(", ")}"
|
65
67
|
end
|
66
68
|
|
67
69
|
def required
|
68
|
-
attributes.select(&:required).map(&:name)
|
70
|
+
attributes.values.select(&:required).map(&:name)
|
69
71
|
end
|
70
72
|
|
71
73
|
end # class Attributes
|
@@ -3,49 +3,74 @@
|
|
3
3
|
describe AttributesDSL do
|
4
4
|
|
5
5
|
let(:coercer) { -> value { value.to_s } }
|
6
|
-
let(:klass)
|
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
|
6
|
+
let(:klass) { Class.new { extend AttributesDSL } }
|
16
7
|
|
17
8
|
before do
|
18
|
-
klass.attribute(:foo,
|
19
|
-
klass.attribute("bar"
|
9
|
+
klass.attribute(:foo, &:to_s)
|
10
|
+
klass.attribute("bar")
|
20
11
|
klass.attribute(:baz, &:to_i)
|
21
12
|
end
|
22
13
|
|
23
14
|
subject { klass.new(arguments) }
|
24
15
|
|
25
|
-
|
26
|
-
|
16
|
+
describe "instance" do
|
17
|
+
|
18
|
+
context "without arguments" do
|
19
|
+
subject { klass.new }
|
27
20
|
|
28
|
-
|
29
|
-
|
21
|
+
it "initializes default attributes" do
|
22
|
+
expect(subject.attributes).to eql(foo: "", bar: nil, baz: 0)
|
23
|
+
end
|
30
24
|
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
context "when all required attributes are set" do
|
27
|
+
let(:arguments) { { bar: :BAR, baz: "42" } }
|
28
|
+
let(:klass) do
|
29
|
+
Class.new do
|
30
|
+
extend AttributesDSL
|
31
|
+
|
32
|
+
def initialize(attributes = {})
|
33
|
+
super
|
34
|
+
IceNine.deep_freeze(self)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "initializes attributes" do
|
40
|
+
expect(subject.attributes).to eql(foo: "", bar: :BAR, baz: 42)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "defines methods for every attribute" do
|
44
|
+
expect(subject.foo).to eql ""
|
45
|
+
expect(subject.bar).to eql :BAR
|
46
|
+
expect(subject.baz).to eql 42
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't freeze argument" do
|
50
|
+
expect { subject }.not_to change { arguments.frozen? }
|
51
|
+
end
|
36
52
|
end
|
37
53
|
|
38
|
-
|
39
|
-
|
54
|
+
context "when a required attribute is missed" do
|
55
|
+
let(:arguments) { { foo: :FOO, baz: "42" } }
|
56
|
+
|
57
|
+
before { klass.attribute("bar", required: true) }
|
58
|
+
|
59
|
+
it "fails" do
|
60
|
+
expect { subject }.to raise_error ArgumentError
|
61
|
+
end
|
40
62
|
end
|
41
|
-
end # context
|
42
63
|
|
43
|
-
|
44
|
-
|
64
|
+
end # describe instance
|
65
|
+
|
66
|
+
describe "subclass" do
|
45
67
|
|
46
|
-
|
47
|
-
|
68
|
+
subject { Class.new(klass) }
|
69
|
+
|
70
|
+
it "inherits parent attributes" do
|
71
|
+
expect(subject.new.attributes).to eql(klass.new.attributes)
|
48
72
|
end
|
49
|
-
|
73
|
+
|
74
|
+
end # describe subclass
|
50
75
|
|
51
76
|
end # describe AttributesDSL
|
@@ -14,19 +14,25 @@ describe AttributesDSL::Attributes do
|
|
14
14
|
describe "#attributes" do
|
15
15
|
subject { attributes.attributes }
|
16
16
|
|
17
|
-
it { is_expected.to be_kind_of
|
17
|
+
it { is_expected.to be_kind_of Hash }
|
18
18
|
it { is_expected.to be_empty }
|
19
19
|
end # describe #attributes
|
20
20
|
|
21
21
|
describe "#register" do
|
22
|
-
subject
|
22
|
+
subject do
|
23
|
+
attributes.register(:bar).register(:foo, default: :FOO, &coercer)
|
24
|
+
end
|
23
25
|
|
24
26
|
it "returns new collection" do
|
25
27
|
expect(subject).to be_kind_of described_class
|
26
28
|
end
|
27
29
|
|
28
|
-
it "
|
29
|
-
|
30
|
+
it "adds attributes" do
|
31
|
+
expect(subject.attributes.keys).to contain_exactly(:bar, :foo)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "uses arguments" do
|
35
|
+
attribute = subject.attributes[:foo]
|
30
36
|
|
31
37
|
expect(attribute).to be_kind_of AttributesDSL::Attribute
|
32
38
|
expect(attribute.name).to eql :foo
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.0.2
|
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
|
+
date: 2015-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ice_nine
|