constructable 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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: 190)
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 elgantely, please contact me!
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.2
1
+ 0.3.3
@@ -5,17 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{constructable}
8
- s.version = "0.3.2"
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-17}
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: ->(hash) { hash[self.name].is_a? self.validate_type }
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, constructor_hash)
37
+ def check_for_requirement(requirement, value)
39
38
  if self.send requirement[:name]
40
- unless self.instance_exec(constructor_hash,&requirement[:check])
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(constructor_hash)
48
- unless constructor_hash[self.name].nil?
46
+ def process(value)
47
+ unless value.nil?
49
48
  REQUIREMENTS.each do |requirement|
50
- check_for_requirement(requirement, constructor_hash)
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 if attribute.readable
50
+ attr_reader attribute.name
51
51
 
52
52
  define_method(:"#{attribute.name}=") do |value|
53
- instance_variable_set attribute.ivar_symbol, attribute.process({ attribute.name => value})
54
- end if attribute.writable
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 |attributes|
62
- obj.instance_variable_set(attributes.ivar_symbol, attributes.process(constructor_hash))
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({foo: false})
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({foo: 'bar'})
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(bar: 'blab')
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(foo: 6)
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(foo: 'notanumber')
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({number: '5'})
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.2
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-17 00:00:00 +02:00
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: "\n\
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: 932073679521130628
103
+ hash: 335237132407385369
108
104
  segments:
109
105
  - 0
110
106
  version: "0"