constructable 0.3.2 → 0.3.3
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.
- data/README.markdown +58 -30
- data/Rakefile +1 -6
- data/VERSION +1 -1
- data/constructable.gemspec +3 -8
- data/lib/constructable/attribute.rb +12 -15
- data/lib/constructable/constructor.rb +7 -5
- data/test/constructable/test_attribute.rb +16 -9
- data/test/constructable/test_constructor.rb +15 -0
- metadata +4 -8
data/README.markdown
CHANGED
@@ -24,33 +24,6 @@ ruby = ProgrammingLanguage.new(name: 'Ruby', creator: 'Yukihiro Matsumoto')
|
|
24
24
|
The object _ruby_ will now have the instance variables _@name_ and
|
25
25
|
_@creator_ set to _'Ruby'_ and _'Yukihiro Matsumoto'_.
|
26
26
|
|
27
|
-
## Does not break initialize behaviour
|
28
|
-
|
29
|
-
You can use initialize just like you'd normally do:
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
class Animal
|
33
|
-
constructable [:biological_class, readable: true]
|
34
|
-
attr_reader :name
|
35
|
-
|
36
|
-
GuessBiologicalClass = { ['Pig', 'Cow', 'Whale'] => 'Mammal', [ 'Turtle', 'Caiman' ] => 'Reptile' }
|
37
|
-
|
38
|
-
def initialize(name, options = {})
|
39
|
-
@name = name
|
40
|
-
@biological_class = GuessBiologicalClass.find { |animals,_| animals.include?(name) }.last if options[:guess_biological_class]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
rhinocerus = Animal.new('Rhinocerus', biological_class: 'Mammal')
|
45
|
-
rhinocerus.biological_class
|
46
|
-
#=> 'Mammal'
|
47
|
-
|
48
|
-
turtle = Animal.new('Turtle', guess_biological_class: true)
|
49
|
-
turtle.biological_class
|
50
|
-
#=> 'Reptile'
|
51
|
-
```
|
52
|
-
|
53
|
-
|
54
27
|
|
55
28
|
## Setters, Getters
|
56
29
|
|
@@ -79,6 +52,34 @@ orient_express.location
|
|
79
52
|
#=> 'Budapest'
|
80
53
|
```
|
81
54
|
|
55
|
+
|
56
|
+
## Does not break initialize behaviour
|
57
|
+
|
58
|
+
You can use initialize just like you'd normally do:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class Animal
|
62
|
+
constructable [:biological_class, readable: true]
|
63
|
+
attr_reader :name
|
64
|
+
|
65
|
+
GuessBiologicalClass = { ['Pig', 'Cow', 'Whale'] => 'Mammal', [ 'Turtle', 'Caiman' ] => 'Reptile' }
|
66
|
+
|
67
|
+
def initialize(name, options = {})
|
68
|
+
@name = name
|
69
|
+
@biological_class = GuessBiologicalClass.find { |animals,_| animals.include?(name) }.last if options[:guess_biological_class]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
rhinocerus = Animal.new('Rhinocerus', biological_class: 'Mammal')
|
74
|
+
rhinocerus.biological_class
|
75
|
+
#=> 'Mammal'
|
76
|
+
|
77
|
+
turtle = Animal.new('Turtle', guess_biological_class: true)
|
78
|
+
turtle.biological_class
|
79
|
+
#=> 'Reptile'
|
80
|
+
```
|
81
|
+
|
82
|
+
|
82
83
|
## Validations
|
83
84
|
|
84
85
|
You can setup validation for constructable attributes, so the users of
|
@@ -121,6 +122,7 @@ big_farm = Farm.new(animals: [:pigs, :cows])
|
|
121
122
|
# raises AttributeError, ':animals has not passed validation'
|
122
123
|
```
|
123
124
|
|
125
|
+
|
124
126
|
## Convert your attributes
|
125
127
|
|
126
128
|
You can pass a converter as an option for a constructable attribute,
|
@@ -139,6 +141,7 @@ small_box.height
|
|
139
141
|
#=> 240
|
140
142
|
```
|
141
143
|
|
144
|
+
|
142
145
|
## Default values
|
143
146
|
|
144
147
|
You can also specify, which values your constructable attributes are set
|
@@ -146,7 +149,7 @@ to by default:
|
|
146
149
|
|
147
150
|
```ruby
|
148
151
|
class Framework
|
149
|
-
constructable :opinionated, default: true
|
152
|
+
constructable :opinionated, default: ->{true}
|
150
153
|
end
|
151
154
|
|
152
155
|
rails = Framework.new
|
@@ -155,6 +158,7 @@ rails.opinionated
|
|
155
158
|
|
156
159
|
```
|
157
160
|
|
161
|
+
|
158
162
|
## Redefining setters and getters
|
159
163
|
|
160
164
|
You can redefine the setters and getters provided by the constructable
|
@@ -173,7 +177,7 @@ class Song
|
|
173
177
|
end
|
174
178
|
end
|
175
179
|
|
176
|
-
song = Song.new(length:
|
180
|
+
song = Song.new(length: '3:10')
|
177
181
|
#=> #<Song:0x000001010ea040 @length=190>
|
178
182
|
|
179
183
|
song.length = '1:30'
|
@@ -184,6 +188,30 @@ song.length = 'abc'
|
|
184
188
|
# raises AttributeError, ':length must be of type Integer'
|
185
189
|
```
|
186
190
|
|
191
|
+
You can also redefine private setters/getters the constructable
|
192
|
+
class macro set up for you:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class Song
|
196
|
+
constructable :name, validate_type: String
|
197
|
+
attribute_reader :name_history
|
198
|
+
|
199
|
+
def initialize(opts = {})
|
200
|
+
@name_history = []
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def name=(name)
|
206
|
+
@name_histoy += name
|
207
|
+
super
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
song = Song.new(name: 'Aaron', length: '6:01')
|
212
|
+
#=> #<Song:0x0x00000100941528 @length=190 @name="Aaron" @name_history=["Aaron"]>
|
213
|
+
```
|
214
|
+
|
187
215
|
## constructable\_attributes method
|
188
216
|
|
189
217
|
You can all the constructable attributes and their values of your class as a hash,
|
@@ -212,7 +240,7 @@ impossible, to provide all the magic of this gem, without defining
|
|
212
240
|
the 'included' macro. I thought about getting rid of some of the magic,
|
213
241
|
but this would infer with my initial intention, to provide an easy way
|
214
242
|
to make constructable classes. If someone has a nice idea, how to solve
|
215
|
-
this problem
|
243
|
+
this problem elgantly, please contact me!
|
216
244
|
|
217
245
|
## Copyright
|
218
246
|
Copyright (c) 2011 Manuel Korfmann. See LICENSE.txt for
|
data/Rakefile
CHANGED
@@ -18,12 +18,7 @@ Jeweler::Tasks.new do |gem|
|
|
18
18
|
gem.homepage = "http://github.com/mkorfmann/constructable"
|
19
19
|
gem.license = "MIT"
|
20
20
|
gem.summary = %Q{Makes constructing objects through an attributes hash easier}
|
21
|
-
gem.description = %Q{
|
22
|
-
Adds the class macro Class#constructable to easily define what attributes a Class accepts provided as a hash to Class#new.
|
23
|
-
Attributes can be configured in their behaviour in case they are not provided or are in the wrong format.
|
24
|
-
Their default value can also be defined and you have granular control on how accessible your attribute is.
|
25
|
-
See the documentation for Constructable::Constructable#constructable or the README for more information.
|
26
|
-
}
|
21
|
+
gem.description = %Q{Makes constructing objects through an attributes hash easier}
|
27
22
|
gem.email = "manu@korfmann.info"
|
28
23
|
gem.authors = ["Manuel Korfmann"]
|
29
24
|
# dependencies defined in Gemfile
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.3
|
data/constructable.gemspec
CHANGED
@@ -5,17 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{constructable}
|
8
|
-
s.version = "0.3.
|
8
|
+
s.version = "0.3.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Manuel Korfmann"]
|
12
|
-
s.date = %q{2011-06-
|
13
|
-
s.description = %q{
|
14
|
-
Adds the class macro Class#constructable to easily define what attributes a Class accepts provided as a hash to Class#new.
|
15
|
-
Attributes can be configured in their behaviour in case they are not provided or are in the wrong format.
|
16
|
-
Their default value can also be defined and you have granular control on how accessible your attribute is.
|
17
|
-
See the documentation for Constructable::Constructable#constructable or the README for more information.
|
18
|
-
}
|
12
|
+
s.date = %q{2011-06-18}
|
13
|
+
s.description = %q{Makes constructing objects through an attributes hash easier}
|
19
14
|
s.email = %q{manu@korfmann.info}
|
20
15
|
s.extra_rdoc_files = [
|
21
16
|
"LICENSE.txt",
|
@@ -2,18 +2,17 @@ module Constructable
|
|
2
2
|
class Attribute
|
3
3
|
ATTRIBUTES = [:group, :writable, :readable, :accessible, :required, :validate, :default, :validate_type, :converter]
|
4
4
|
attr_accessor *ATTRIBUTES, :name
|
5
|
-
attr_reader :value
|
6
5
|
|
7
6
|
REQUIREMENTS = [
|
8
|
-
{
|
9
|
-
name: :validate,
|
10
|
-
message: proc {":#{self.name} did not pass validation"},
|
11
|
-
check: ->(hash) { self.validate.call(hash[self.name])}
|
12
|
-
},
|
13
7
|
{
|
14
8
|
name: :validate_type,
|
15
9
|
message: proc {":#{self.name} must be of type #{self.validate_type}"},
|
16
|
-
check: ->(
|
10
|
+
check: ->(value) { value.is_a? self.validate_type }
|
11
|
+
},
|
12
|
+
{
|
13
|
+
name: :validate,
|
14
|
+
message: proc {":#{self.name} did not pass validation"},
|
15
|
+
check: ->(value) { self.validate.call(value)}
|
17
16
|
}
|
18
17
|
]
|
19
18
|
|
@@ -35,26 +34,24 @@ module Constructable
|
|
35
34
|
('@' + self.name.to_s).to_sym
|
36
35
|
end
|
37
36
|
|
38
|
-
def check_for_requirement(requirement,
|
37
|
+
def check_for_requirement(requirement, value)
|
39
38
|
if self.send requirement[:name]
|
40
|
-
unless self.instance_exec(
|
39
|
+
unless self.instance_exec(value,&requirement[:check])
|
41
40
|
raise AttributeError, instance_eval(&requirement[:message])
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
45
44
|
private :check_for_requirement
|
46
45
|
|
47
|
-
def process(
|
48
|
-
unless
|
46
|
+
def process(value)
|
47
|
+
unless value.nil?
|
49
48
|
REQUIREMENTS.each do |requirement|
|
50
|
-
check_for_requirement(requirement,
|
49
|
+
check_for_requirement(requirement, value)
|
51
50
|
end
|
52
|
-
|
53
|
-
value = constructor_hash[self.name]
|
54
51
|
self.converter ? converter.(value) : value
|
55
52
|
else
|
56
53
|
raise AttributeError, ":#{self.name} is a required attribute" if self.required
|
57
|
-
self.default
|
54
|
+
self.default.call if self.default
|
58
55
|
end
|
59
56
|
end
|
60
57
|
end
|
@@ -47,19 +47,21 @@ module Constructable
|
|
47
47
|
attributes = @attributes
|
48
48
|
attributes.each do |attribute|
|
49
49
|
@module.module_eval do
|
50
|
-
attr_reader attribute.name
|
50
|
+
attr_reader attribute.name
|
51
51
|
|
52
52
|
define_method(:"#{attribute.name}=") do |value|
|
53
|
-
instance_variable_set attribute.ivar_symbol, attribute.process(
|
54
|
-
end
|
53
|
+
instance_variable_set attribute.ivar_symbol, attribute.process(value)
|
54
|
+
end
|
55
|
+
private attribute.name unless attribute.readable
|
56
|
+
private :"#{attribute.name}=" unless attribute.writable
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
59
61
|
def construct(constructor_hash, obj)
|
60
62
|
constructor_hash ||= {}
|
61
|
-
@attributes.each do |
|
62
|
-
obj.
|
63
|
+
@attributes.each do |attribute|
|
64
|
+
obj.send :"#{attribute.name}=", constructor_hash[attribute.name]
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
@@ -19,19 +19,19 @@ describe 'Attribute' do
|
|
19
19
|
describe 'process' do
|
20
20
|
it 'intepretes nil but not false as undefined' do
|
21
21
|
attribute = Attribute.new(:foo, default: true)
|
22
|
-
assert_equal false, attribute.process(
|
22
|
+
assert_equal false, attribute.process(false)
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should raise nothing if no attributes are specified' do
|
26
26
|
attribute = Attribute.new(:foo)
|
27
|
-
assert_equal 'bar', attribute.process(
|
27
|
+
assert_equal 'bar', attribute.process('bar')
|
28
28
|
end
|
29
29
|
|
30
30
|
describe 'required attribute' do
|
31
31
|
it 'should raise an AttributeError if required is set to true' do
|
32
32
|
attribute = Attribute.new(:foo, required: true)
|
33
33
|
begin
|
34
|
-
attribute.process(
|
34
|
+
attribute.process(nil)
|
35
35
|
rescue Exception => e
|
36
36
|
assert AttributeError === e
|
37
37
|
assert_equal ':foo is a required attribute', e.message
|
@@ -45,16 +45,23 @@ describe 'Attribute' do
|
|
45
45
|
it 'does not check for further requirements' do
|
46
46
|
attribute = Attribute.new(:foo, validate_type: Integer)
|
47
47
|
refute_raises do
|
48
|
-
attribute.process(
|
48
|
+
attribute.process(nil)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
describe 'validator' do
|
54
|
+
it 'checks for validate_type first' do
|
55
|
+
attribute = Attribute.new(:array, validate_type: Array, validate: ->(value) { value.all? { |s| String === s }})
|
56
|
+
assert_raises AttributeError do
|
57
|
+
attribute.process('obviously not an array')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
54
61
|
it 'should raise an AttributeError if the validator doesn\'t pass' do
|
55
62
|
attribute = Attribute.new(:foo, validate: ->(number) { number < 5 })
|
56
63
|
begin
|
57
|
-
attribute.process(
|
64
|
+
attribute.process(6)
|
58
65
|
rescue Exception => e
|
59
66
|
assert AttributeError === e, "[#{e.class},#{e.message}] was not expected"
|
60
67
|
assert_equal ':foo did not pass validation', e.message
|
@@ -68,7 +75,7 @@ describe 'Attribute' do
|
|
68
75
|
it 'should raise an AttributeError if the value has not the wanted validate_type' do
|
69
76
|
attribute = Attribute.new(:foo, validate_type: Integer)
|
70
77
|
begin
|
71
|
-
attribute.process(
|
78
|
+
attribute.process('notanumber')
|
72
79
|
rescue Exception => e
|
73
80
|
assert AttributeError === e, "[#{e.class},#{e.message}] was not expected"
|
74
81
|
assert_equal ':foo must be of type Integer', e.message
|
@@ -80,15 +87,15 @@ describe 'Attribute' do
|
|
80
87
|
|
81
88
|
describe 'default value' do
|
82
89
|
it 'should be possible to provide a default value' do
|
83
|
-
attribute = Attribute.new(:foo, default: :bar)
|
84
|
-
assert_equal :bar, attribute.process(
|
90
|
+
attribute = Attribute.new(:foo, default: ->{ :bar })
|
91
|
+
assert_equal :bar, attribute.process(nil)
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
88
95
|
describe 'convert value' do
|
89
96
|
it 'is possible to define a converter(proc) which converts attribute values' do
|
90
97
|
attribute = Attribute.new(:number, converter: ->(value) { value.to_i })
|
91
|
-
assert_equal 5, attribute.process(
|
98
|
+
assert_equal 5, attribute.process('5')
|
92
99
|
end
|
93
100
|
end
|
94
101
|
end
|
@@ -22,6 +22,14 @@ describe 'Constructor' do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe 'redefining' do
|
25
|
+
describe 'constructing a class will call redefined setters' do
|
26
|
+
it 'calls the redefined setters' do
|
27
|
+
@klass.constructable :bacon, readable: true
|
28
|
+
@klass.class_eval { private; def bacon=(value);super(:zomg_bacon);end }
|
29
|
+
instance = @klass.new(bacon: :no_bacon)
|
30
|
+
assert_equal :zomg_bacon, instance.bacon
|
31
|
+
end
|
32
|
+
end
|
25
33
|
|
26
34
|
describe 'class' do
|
27
35
|
it 'getters ' do
|
@@ -88,6 +96,13 @@ describe 'Constructor' do
|
|
88
96
|
instance.integer = :not_an_integer
|
89
97
|
end
|
90
98
|
end
|
99
|
+
|
100
|
+
it 'sets for private setters/getters' do
|
101
|
+
@klass.constructable :integer, validate_type: Integer, readable: true
|
102
|
+
@klass.class_eval { private; def integer=(value); super(value.to_i) ;end }
|
103
|
+
instance = @klass.new(integer: '5')
|
104
|
+
assert_equal 5, instance.integer
|
105
|
+
end
|
91
106
|
end
|
92
107
|
end
|
93
108
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: constructable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.3.
|
5
|
+
version: 0.3.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Manuel Korfmann
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-18 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -57,11 +57,7 @@ dependencies:
|
|
57
57
|
type: :development
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: *id004
|
60
|
-
description:
|
61
|
-
Adds the class macro Class#constructable to easily define what attributes a Class accepts provided as a hash to Class#new.\n\
|
62
|
-
Attributes can be configured in their behaviour in case they are not provided or are in the wrong format.\n\
|
63
|
-
Their default value can also be defined and you have granular control on how accessible your attribute is.\n\
|
64
|
-
See the documentation for Constructable::Constructable#constructable or the README for more information.\n "
|
60
|
+
description: Makes constructing objects through an attributes hash easier
|
65
61
|
email: manu@korfmann.info
|
66
62
|
executables: []
|
67
63
|
|
@@ -104,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
100
|
requirements:
|
105
101
|
- - ">="
|
106
102
|
- !ruby/object:Gem::Version
|
107
|
-
hash:
|
103
|
+
hash: 335237132407385369
|
108
104
|
segments:
|
109
105
|
- 0
|
110
106
|
version: "0"
|