attributed_object 0.2.1
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 +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +222 -0
- data/Rakefile +1 -0
- data/attributed_object.gemspec +24 -0
- data/benchmark_attributed_object.rb +108 -0
- data/lib/attributed_object.rb +67 -0
- data/lib/attributed_object/base.rb +106 -0
- data/lib/attributed_object/coerce.rb +30 -0
- data/lib/attributed_object/strict.rb +28 -0
- data/lib/attributed_object/type.rb +11 -0
- data/lib/attributed_object/types/array_of.rb +25 -0
- data/lib/attributed_object/types/hash_of.rb +28 -0
- data/lib/attributed_object/version.rb +3 -0
- data/lib/attributed_object_helpers/hash_util.rb +21 -0
- data/lib/attributed_object_helpers/type_check.rb +46 -0
- data/lib/attributed_object_helpers/type_coerce.rb +52 -0
- data/spec/attributed_object/coerce_spec.rb +159 -0
- data/spec/attributed_object/strict_spec.rb +129 -0
- data/spec/attributed_object_spec.rb +228 -0
- metadata +110 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'attributed_object'
|
2
|
+
|
3
|
+
describe AttributedObject::Strict do
|
4
|
+
class SimpleFoo
|
5
|
+
include AttributedObject::Strict
|
6
|
+
attribute :bar
|
7
|
+
end
|
8
|
+
|
9
|
+
class TypedFoo
|
10
|
+
include AttributedObject::Strict
|
11
|
+
attribute :a_string, :string, default: 'its a string'
|
12
|
+
attribute :a_boolean, :boolean, default: false
|
13
|
+
attribute :a_integer, :integer, default: 77
|
14
|
+
attribute :a_float, :float, default: 98.12
|
15
|
+
attribute :a_numeric, :numeric, default: 12.12
|
16
|
+
attribute :a_symbol, :symbol, default: :some_default_symbol
|
17
|
+
attribute :a_string_by_class, String, default: 'some default string'
|
18
|
+
attribute :another_class, SimpleFoo, default: nil
|
19
|
+
attribute :a_array, :array, default: nil
|
20
|
+
attribute :a_array_of, ArrayOf(:integer), default: nil
|
21
|
+
attribute :a_hash, :hash, default: nil
|
22
|
+
attribute :a_hash_of, HashOf(:string, :integer), default: nil
|
23
|
+
end
|
24
|
+
|
25
|
+
class TypedFooWithOption
|
26
|
+
include AttributedObject::Strict
|
27
|
+
attributed_object type_check: :strict
|
28
|
+
attribute :a_string, :string, default: 'its a string'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can be set with option' do
|
32
|
+
expect { TypedFooWithOption.new(a_string: :its_a_symbol) }.to raise_error(AttributedObject::TypeError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can handle primitive ruby types' do
|
36
|
+
f = TypedFoo.new(
|
37
|
+
a_boolean: true,
|
38
|
+
a_integer: 12,
|
39
|
+
a_float: 42.7,
|
40
|
+
a_numeric: 35.9,
|
41
|
+
a_symbol: :my_symbol,
|
42
|
+
a_string_by_class: 'my class string check',
|
43
|
+
another_class: SimpleFoo.new(bar: 'hi'),
|
44
|
+
a_array: ['1'],
|
45
|
+
a_array_of: [1],
|
46
|
+
a_hash: {foo: 'bar'},
|
47
|
+
a_hash_of: { 'hello' => 1 },
|
48
|
+
)
|
49
|
+
|
50
|
+
expect(f.a_string).to eq('its a string')
|
51
|
+
expect(f.a_boolean).to eq(true)
|
52
|
+
expect(f.a_integer).to eq(12)
|
53
|
+
expect(f.a_float).to eq(42.7)
|
54
|
+
expect(f.a_numeric).to eq(35.9)
|
55
|
+
expect(f.a_symbol).to eq(:my_symbol)
|
56
|
+
expect(f.a_string_by_class).to eq('my class string check')
|
57
|
+
expect(f.another_class).to eq(SimpleFoo.new(bar: 'hi'))
|
58
|
+
expect(f.a_array).to eq(['1'])
|
59
|
+
expect(f.a_array_of).to eq([1])
|
60
|
+
expect(f.a_hash).to eq({foo: 'bar'})
|
61
|
+
expect(f.a_hash_of).to eq({'hello' => 1})
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises error on non-string' do
|
65
|
+
expect { TypedFoo.new(a_string: :its_a_symbol) }.to raise_error(AttributedObject::TypeError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'raises error on non-bool' do
|
69
|
+
expect { TypedFoo.new(a_boolean: 42) }.to raise_error(AttributedObject::TypeError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'raises error on non-integer' do
|
73
|
+
expect { TypedFoo.new(a_integer: '42') }.to raise_error(AttributedObject::TypeError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'raises error on non-float' do
|
77
|
+
expect { TypedFoo.new(a_float: 42) }.to raise_error(AttributedObject::TypeError)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'raises error on non-symbol' do
|
81
|
+
expect { TypedFoo.new(a_symbol: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises error on non-numeric' do
|
85
|
+
expect { TypedFoo.new(a_numeric: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises error on non-strings when string is defined by class' do
|
89
|
+
expect { TypedFoo.new(a_string_by_class: 42) }.to raise_error(AttributedObject::TypeError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises error on non SimpleFoos' do
|
93
|
+
expect { TypedFoo.new(another_class: 42) }.to raise_error(AttributedObject::TypeError)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'raises error on non-array' do
|
97
|
+
expect { TypedFoo.new(a_array: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'raises error on array with wrong contents' do
|
101
|
+
expect { TypedFoo.new(a_array_of: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
102
|
+
expect { TypedFoo.new(a_array_of: ['its a string']) }.to raise_error(AttributedObject::TypeError)
|
103
|
+
expect { TypedFoo.new(a_array_of: [1]) }.not_to raise_error
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'raises error on non-hash' do
|
107
|
+
expect { TypedFoo.new(a_hash: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'raises error on hash with wrong contents' do
|
111
|
+
expect { TypedFoo.new(a_hash_of: 'its a string') }.to raise_error(AttributedObject::TypeError)
|
112
|
+
expect { TypedFoo.new(a_hash_of: { 'foo' => '1' }) }.to raise_error(AttributedObject::TypeError)
|
113
|
+
expect { TypedFoo.new(a_hash_of: { 1 => 1 }) }.to raise_error(AttributedObject::TypeError)
|
114
|
+
expect { TypedFoo.new(a_hash_of: { 'foo' => 1 }) }.not_to raise_error
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'raises no errors for nil values' do
|
118
|
+
expect { TypedFoo.new(a_string: nil) }.not_to raise_error
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'raises on unknown type' do
|
122
|
+
expect do
|
123
|
+
class Miau
|
124
|
+
include AttributedObject::Strict
|
125
|
+
attribute :something, :does_not_exist
|
126
|
+
end
|
127
|
+
end.to raise_error(AttributedObject::ConfigurationError)
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'attributed_object'
|
2
|
+
|
3
|
+
describe AttributedObject do
|
4
|
+
class SimpleFoo
|
5
|
+
include AttributedObject::Strict
|
6
|
+
attribute :bar
|
7
|
+
end
|
8
|
+
|
9
|
+
class DisallowingNil
|
10
|
+
include AttributedObject::Strict
|
11
|
+
attribute :bar, disallow: nil
|
12
|
+
end
|
13
|
+
|
14
|
+
class DefaultFoo
|
15
|
+
include AttributedObject::Strict
|
16
|
+
attribute :bar, default: "my default"
|
17
|
+
attribute :dynamic, default: -> { count }
|
18
|
+
|
19
|
+
def self.reset
|
20
|
+
@count = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.count
|
24
|
+
@count ||= 0
|
25
|
+
@count += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ChildFoo < DefaultFoo
|
30
|
+
attribute :lollipop, default: "lecker"
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'requires attributes by default' do
|
34
|
+
expect { SimpleFoo.new }.to raise_error(AttributedObject::MissingAttributeError)
|
35
|
+
expect(SimpleFoo.new(bar: 1).bar).to eq(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'nil control' do
|
39
|
+
it 'allows explicit nil values' do
|
40
|
+
expect(SimpleFoo.new(bar: nil).bar).to eq(nil)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'can be controlled to not allow explicit nil' do
|
44
|
+
expect { DisallowingNil.new(bar: nil).bar }.to raise_error(AttributedObject::DisallowedValueError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'default value' do
|
49
|
+
before { DefaultFoo.reset }
|
50
|
+
|
51
|
+
it 'can specify a default value' do
|
52
|
+
expect(DefaultFoo.new.bar).to eq("my default")
|
53
|
+
expect(DefaultFoo.new(bar: 'other').bar).to eq("other")
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'can specify a lambda as default value' do
|
57
|
+
expect(DefaultFoo.new.dynamic).to eq(1)
|
58
|
+
expect(DefaultFoo.new.dynamic).to eq(2)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'extra_options' do
|
63
|
+
context 'inheritance' do
|
64
|
+
it 'is passed to the children' do
|
65
|
+
class Papa
|
66
|
+
include AttributedObject::Strict
|
67
|
+
attributed_object default_to: nil
|
68
|
+
|
69
|
+
attribute :foo
|
70
|
+
attribute :bar, default: 'hi'
|
71
|
+
end
|
72
|
+
|
73
|
+
class Sohn < Papa
|
74
|
+
attribute :something_else
|
75
|
+
end
|
76
|
+
|
77
|
+
expect(Sohn.new.something_else).to eq(nil)
|
78
|
+
expect(Sohn.new.bar).to eq('hi')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'default_to' do
|
83
|
+
it 'allows changing default for all fields' do
|
84
|
+
class Defaulting
|
85
|
+
include AttributedObject::Strict
|
86
|
+
attributed_object default_to: nil
|
87
|
+
|
88
|
+
attribute :foo
|
89
|
+
attribute :bar, default: 'hi'
|
90
|
+
end
|
91
|
+
|
92
|
+
expect(Defaulting.new.foo).to eq(nil)
|
93
|
+
expect(Defaulting.new.bar).to eq('hi')
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'allows type defaulting' do
|
97
|
+
class TypeDefaulting
|
98
|
+
include AttributedObject::Strict
|
99
|
+
attributed_object default_to: AttributedObject::TypeDefaults.new
|
100
|
+
|
101
|
+
attribute :a_string, :string
|
102
|
+
attribute :a_boolean, :boolean
|
103
|
+
attribute :a_integer, :integer
|
104
|
+
attribute :a_float, :float
|
105
|
+
attribute :a_numeric, :numeric
|
106
|
+
attribute :a_symbol, :symbol
|
107
|
+
attribute :a_string_by_class, String
|
108
|
+
attribute :another_class, SimpleFoo
|
109
|
+
attribute :a_array, :array
|
110
|
+
attribute :a_hash, :hash
|
111
|
+
attribute :something_with_default, :string, default: 'foobar'
|
112
|
+
attribute :something_without_type
|
113
|
+
end
|
114
|
+
|
115
|
+
expect(TypeDefaulting.new.a_string).to eq('')
|
116
|
+
expect(TypeDefaulting.new.a_boolean).to eq(false)
|
117
|
+
expect(TypeDefaulting.new.a_integer).to eq(0)
|
118
|
+
expect(TypeDefaulting.new.a_float).to eq(0.0)
|
119
|
+
expect(TypeDefaulting.new.a_numeric).to eq(0)
|
120
|
+
expect(TypeDefaulting.new.a_symbol).to eq(nil)
|
121
|
+
expect(TypeDefaulting.new.a_string_by_class).to eq(nil)
|
122
|
+
expect(TypeDefaulting.new.another_class).to eq(nil)
|
123
|
+
expect(TypeDefaulting.new.a_array).to eq([])
|
124
|
+
expect(TypeDefaulting.new.a_hash).to eq({})
|
125
|
+
expect(TypeDefaulting.new.something_with_default).to eq('foobar')
|
126
|
+
expect(TypeDefaulting.new.something_without_type).to eq(nil)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'is possible to overwrite and add type defaults' do
|
130
|
+
class TypeDefaultingOverwrites
|
131
|
+
include AttributedObject::Strict
|
132
|
+
attributed_object default_to: AttributedObject::TypeDefaults.new(
|
133
|
+
:string => 'my_default_string',
|
134
|
+
SimpleFoo => SimpleFoo.new(bar: 'kekse')
|
135
|
+
)
|
136
|
+
|
137
|
+
attribute :a_string, :string
|
138
|
+
attribute :a_integer, :integer
|
139
|
+
attribute :foo, SimpleFoo
|
140
|
+
end
|
141
|
+
|
142
|
+
expect(TypeDefaultingOverwrites.new.a_string).to eq('my_default_string')
|
143
|
+
expect(TypeDefaulting.new.a_integer).to eq(0)
|
144
|
+
expect(TypeDefaultingOverwrites.new.foo).to eq(SimpleFoo.new(bar: 'kekse'))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'ignore_extra_keys' do
|
149
|
+
it 'allows extra_keys' do
|
150
|
+
class FooWithExtra
|
151
|
+
include AttributedObject::Strict
|
152
|
+
attributed_object ignore_extra_keys: true
|
153
|
+
attribute :bar, :integer
|
154
|
+
end
|
155
|
+
|
156
|
+
expect { FooWithExtra.new(bar: 12, not_defined: 'asd') }.not_to raise_error
|
157
|
+
expect(FooWithExtra.new(bar: 12, not_defined: 'asd').attributes).to eq(bar: 12)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'throws an error for unknown attributes' do
|
163
|
+
expect { SimpleFoo.new(whatever: 'xxx') }.to raise_error(AttributedObject::UnknownAttributeError)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'inherits the attributes from its superclass' do
|
167
|
+
f = ChildFoo.new
|
168
|
+
expect(f.bar).to eq("my default")
|
169
|
+
expect(f.lollipop).to eq("lecker")
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'does not modify the args' do
|
173
|
+
args = {bar: "asd"}
|
174
|
+
f = SimpleFoo.new(args)
|
175
|
+
f.bar = 'different'
|
176
|
+
expect(f.bar).to eq('different')
|
177
|
+
expect(args[:bar]).to eq('asd')
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '#==' do
|
181
|
+
it 'is equals for same attributes' do
|
182
|
+
expect(SimpleFoo.new(bar: 12)).to eq(SimpleFoo.new(bar: 12))
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'is not equal for different attributes' do
|
186
|
+
expect(SimpleFoo.new(bar: 77)).to_not eq(SimpleFoo.new(bar: 12))
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe 'attribute storage' do
|
191
|
+
class InnerStructureFoo
|
192
|
+
include AttributedObject::Coerce
|
193
|
+
attribute :bar, :string, default: 'default'
|
194
|
+
attribute :foo, :string, default: 'default'
|
195
|
+
attribute :number, :integer, default: 0
|
196
|
+
|
197
|
+
def foo=(f)
|
198
|
+
@foo = "prefix-#{f}-suffix"
|
199
|
+
end
|
200
|
+
|
201
|
+
def number=(n)
|
202
|
+
@number = n+1
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe '#attributes' do
|
207
|
+
it 'returns the attributes as hash' do
|
208
|
+
expect(InnerStructureFoo.new(bar: 'hi').attributes).to eq(
|
209
|
+
bar: 'hi',
|
210
|
+
foo: 'prefix-default-suffix',
|
211
|
+
number: 1
|
212
|
+
)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'stores the data in instance vars' do
|
217
|
+
expect(InnerStructureFoo.new(bar: 'hi').instance_variable_get('@bar')).to eq('hi')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'uses setters' do
|
221
|
+
expect(InnerStructureFoo.new(foo: 'middel').foo).to eq('prefix-middel-suffix')
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'uses setters after coercion' do
|
225
|
+
expect(InnerStructureFoo.new(number: '42').number).to eq(43)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attributed_object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jaap Groeneveld
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Simple and fast module for named arguments in model initializers
|
56
|
+
email:
|
57
|
+
- jgroeneveld@me.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- attributed_object.gemspec
|
68
|
+
- benchmark_attributed_object.rb
|
69
|
+
- lib/attributed_object.rb
|
70
|
+
- lib/attributed_object/base.rb
|
71
|
+
- lib/attributed_object/coerce.rb
|
72
|
+
- lib/attributed_object/strict.rb
|
73
|
+
- lib/attributed_object/type.rb
|
74
|
+
- lib/attributed_object/types/array_of.rb
|
75
|
+
- lib/attributed_object/types/hash_of.rb
|
76
|
+
- lib/attributed_object/version.rb
|
77
|
+
- lib/attributed_object_helpers/hash_util.rb
|
78
|
+
- lib/attributed_object_helpers/type_check.rb
|
79
|
+
- lib/attributed_object_helpers/type_coerce.rb
|
80
|
+
- spec/attributed_object/coerce_spec.rb
|
81
|
+
- spec/attributed_object/strict_spec.rb
|
82
|
+
- spec/attributed_object_spec.rb
|
83
|
+
homepage: ''
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.5.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Simple and fast module for named arguments in model initializers
|
107
|
+
test_files:
|
108
|
+
- spec/attributed_object/coerce_spec.rb
|
109
|
+
- spec/attributed_object/strict_spec.rb
|
110
|
+
- spec/attributed_object_spec.rb
|