attributes_dsl 0.0.1 → 0.0.2
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/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
|