constructable 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,6 @@
1
1
  source "http://rubygems.org"
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
4
 
6
5
  # Add dependencies to develop your gem here.
7
6
  # Include everything needed to run rake, tests, features, etc.
data/README.markdown CHANGED
@@ -2,53 +2,217 @@
2
2
 
3
3
  Provides a powerful class macro for defining and configuring constructable attributes of a class.
4
4
 
5
- ## Facts
6
5
 
7
- * Doesn't break default initialize behaviour(see test/constructable/test_constructable 'should not break the initialize behaviour')
8
- * Only does what you expect it to do (no real magic involved)
9
- * Validates your attributes
10
- * Provides a granular control on how accessible your attributes are(no need to define attr_* yourself)
6
+ ## Basic usage
7
+
8
+ Define your class like this:
11
9
 
12
- ## Usage
13
10
  ```ruby
14
- class Foo
15
- constructable [:bar, :readable => true], [:baz, :required => true, :readable => true]
11
+ require 'constructable'
12
+ class ProgrammingLanguage
13
+ constructable :creator, :name
16
14
  end
15
+ ```
16
+
17
+ Then you can construct objects by providing a hash as the last argument
18
+ to ProgrammingLanguage.new like this:
19
+
20
+ ```ruby
21
+ ruby = ProgrammingLanguage.new(name: 'Ruby', creator: 'Yukihiro Matsumoto')
22
+ ```
17
23
 
18
- foo = Foo.new(bar: 5)
19
- # raises AttributeError, ':baz is a required attribute'
24
+ The object _ruby_ will now have the instance variables _@name_ and
25
+ _@creator_ set to _'Ruby'_ and _'Yukihiro Matsumoto'_.
20
26
 
21
- foo = Foo.new(baz: 7, bar: 5)
27
+ ## Does not break initialize behaviour
22
28
 
23
- foo.bar
24
- # => 5
25
- foo.baz
26
- # => 7
29
+ You can use initialize just like you'd normally do:
27
30
 
28
- class ProgrammingLanguage
29
- constructable [:paradigms,
30
- readable: true,
31
- required: true,
32
- validate_type: Array]
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
+
55
+ ## Setters, Getters
56
+
57
+ You can define your constructable attributes as readable, writable or
58
+ both:
59
+
60
+ ```ruby
61
+ class Train
62
+ constructable :speed, readable: true
63
+ constructable :next_stop, writable: true
64
+ constructable :location, accsessible: true
65
+ end
66
+
67
+ orient_express = Train.new(speed: 100)
68
+ orient_express.speed
69
+ #=> 100
70
+
71
+ orient_express.next_stop = 'Bucarest'
72
+ # @next_stop == "Bucarest"
73
+
74
+ orient_express.next_stop
75
+ # raises NoMethodError
76
+
77
+ orient_express.location = 'Budapest'
78
+ orient_express.location
79
+ #=> 'Budapest'
80
+ ```
81
+
82
+ ## Validations
83
+
84
+ You can setup validation for constructable attributes, so the users of
85
+ your api won't provide weird values or none at all:
86
+
87
+ ### required
88
+
89
+ ```ruby
90
+ class Holidays
91
+ constructable :when, required: true
92
+ end
93
+
94
+ summer_holidays = Holidays.new
95
+ # raises AttributeError, ':when is a required attribute'
96
+ ```
97
+
98
+ ### validate\_type
99
+
100
+ ```ruby
101
+ class Conference
102
+ constructable :attendees, validate_type: Integer
33
103
  end
34
104
 
35
- c = ProgrammingLanguage.new(paradigms: :functional)
36
- # raises AttributeError, ':paradigms needs to be of type Array'
105
+ euruko = Conference.new('~300') # btw, euruko was really great!
106
+ # raises AttributeError, ':attendees must be of type Integer'
107
+ ```
108
+
109
+ ### validate
110
+
111
+ ```ruby
112
+ class Farm
113
+ costructable :animals,
114
+ validate_type: Array,
115
+ validate: ->(array_of_animals) do
116
+ array_of_animals.all? { |animal| animal.is_a?(String)
117
+ end
118
+ end
119
+
120
+ big_farm = Farm.new(animals: [:pigs, :cows])
121
+ # raises AttributeError, ':animals has not passed validation'
122
+ ```
123
+
124
+ ## Convert your attributes
125
+
126
+ You can pass a converter as an option for a constructable attribute,
127
+ so before attributes are set, their values get converted to the return
128
+ value of the proc, you provided:
129
+
130
+ ```ruby
131
+ class Box
132
+ constructable :width, :height, converter: ->(value) { value.to_f * 100 }
133
+ end
134
+
135
+ small_box = Box.new(width: '1.40', height: '2.40')
136
+ small_box.width
137
+ #=> 140
138
+ small_box.height
139
+ #=> 240
140
+ ```
141
+
142
+ ## Default values
143
+
144
+ You can also specify, which values your constructable attributes are set
145
+ to by default:
146
+
147
+ ```ruby
148
+ class Framework
149
+ constructable :opinionated, default: true
150
+ end
151
+
152
+ rails = Framework.new
153
+ rails.opinionated
154
+ #=> true
37
155
 
38
- ruby = ProgrammingLanguage.new(paradigms: [:object_oriented, :functional])
39
- ruby.paradigms
40
- # => [:object_oriented, :functional]
41
156
  ```
42
157
 
158
+ ## Redefining setters and getters
159
+
160
+ You can redefine the setters and getters provided by the constructable
161
+ macro and still get all the validations and stuff by calling super:
162
+
163
+ ```ruby
164
+ class Song
165
+ constructable :length, accessible: true, validate_type: Integer
166
+
167
+ def length=(length)
168
+ if length.is_a?(String) && length =~ /(\d{,2}):(\d{,2})/
169
+ @length = $1.to_i * 60 + $2.to_i
170
+ else
171
+ super
172
+ end
173
+ end
174
+ end
175
+
176
+ song = Song.new(length: 190)
177
+ #=> #<Song:0x000001010ea040 @length=190>
178
+
179
+ song.length = '1:30'
180
+ song.length
181
+ #=> 90
182
+
183
+ song.length = 'abc'
184
+ # raises AttributeError, ':length must be of type Integer'
185
+ ```
186
+
187
+ ## constructable\_attributes method
188
+
189
+ You can all the constructable attributes and their values of your class as a hash,
190
+ by calling the constructable_attributes method from within an instance
191
+ of your class:
192
+
193
+ ```ruby
194
+ class Gadget
195
+ constructable :fancyness, :name, :price
196
+ end
197
+
198
+ iphone_4 = Gadget.new(fancyness: 1.0/0, name: 'Iphone 4', price: 1.0/0)
199
+ iphone_4.constructable_attributes
200
+ #=> { :fancyness => Infinity, :name => "Iphone 4", :price => Infinity }
201
+ ```
202
+
203
+ ## Modules
204
+
205
+ ### WARNING
43
206
 
44
- ## Contributing to constructable
45
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
46
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
47
- * Fork the project
48
- * Start a feature/bugfix branch
49
- * Commit and push until you are happy with your contribution
50
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
51
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
207
+ The constructable macro also works for modules(not for themself, but for
208
+ the classes, you include them) but it will define the 'included' hook.
209
+ So if you want to use the 'included' hook yourself, you either need to
210
+ around alias it or to use another gem. I tried some things, but it is
211
+ impossible, to provide all the magic of this gem, without defining
212
+ the 'included' macro. I thought about getting rid of some of the magic,
213
+ but this would infer with my initial intention, to provide an easy way
214
+ to make constructable classes. If someone has a nice idea, how to solve
215
+ this problem elgantely, please contact me!
52
216
 
53
217
  ## Copyright
54
218
  Copyright (c) 2011 Manuel Korfmann. See LICENSE.txt for
data/TODO.md ADDED
@@ -0,0 +1,2 @@
1
+
2
+ * costum error messages for the validator proc
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{constructable}
8
- s.version = "0.3.1"
8
+ s.version = "0.3.2"
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-15}
12
+ s.date = %q{2011-06-17}
13
13
  s.description = %q{
14
14
  Adds the class macro Class#constructable to easily define what attributes a Class accepts provided as a hash to Class#new.
15
15
  Attributes can be configured in their behaviour in case they are not provided or are in the wrong format.
@@ -29,6 +29,7 @@ See the documentation for Constructable::Constructable#constructable or the READ
29
29
  "LICENSE.txt",
30
30
  "README.markdown",
31
31
  "Rakefile",
32
+ "TODO.md",
32
33
  "VERSION",
33
34
  "constructable.gemspec",
34
35
  "lib/constructable.rb",
@@ -12,7 +12,7 @@ module Constructable
12
12
  },
13
13
  {
14
14
  name: :validate_type,
15
- message: proc {":#{self.name} needs to be of type #{self.validate_type}"},
15
+ message: proc {":#{self.name} must be of type #{self.validate_type}"},
16
16
  check: ->(hash) { hash[self.name].is_a? self.validate_type }
17
17
  }
18
18
  ]
@@ -7,9 +7,28 @@ module Constructable
7
7
  @module = Module.new
8
8
  @base = base
9
9
  @base.send :include, @module
10
+ self.redefine_new_calling
10
11
  self.define_concstructable_attributes_method
11
12
  end
12
13
 
14
+ def redefine_new_calling
15
+ case @base
16
+ when Class
17
+ self.redefine_new(@base)
18
+ when Module
19
+ constructor = self
20
+ redefine_new_logic = proc do |base|
21
+ base.instance_variable_set(:@constructor, constructor)
22
+ if Class == base.class
23
+ constructor.redefine_new(base)
24
+ elsif Module == base.class
25
+ base.define_singleton_method :included, &redefine_new_logic
26
+ end
27
+ end
28
+ @base.define_singleton_method :included, &redefine_new_logic
29
+ end
30
+ end
31
+
13
32
  def redefine_new(klass)
14
33
  constructor = self
15
34
  klass.define_singleton_method(:new) do |*args, &block|
@@ -45,8 +64,14 @@ module Constructable
45
64
  end
46
65
 
47
66
  def generate_attributes(attributes)
67
+ options = if Hash === attributes.last
68
+ attributes.pop
69
+ else
70
+ {}
71
+ end
72
+
48
73
  attributes.map do |attribute|
49
- Attribute.new(*attribute)
74
+ Attribute.new(attribute, options)
50
75
  end
51
76
  end
52
77
 
@@ -5,7 +5,7 @@ module Constructable
5
5
  # @example
6
6
  #
7
7
  # class Foo
8
- # constructable [:bar, :readable => true], [:baz, :required => true, :readable => true]
8
+ # constructable :bar, :readable => true, :baz, :readable => true, required: true
9
9
  # end
10
10
  #
11
11
  # foo = Foo.new(bar: 5)
@@ -19,10 +19,10 @@ module Constructable
19
19
  # # => 7
20
20
  #
21
21
  # class ProgrammingLanguage
22
- # constructable [:paradigms,
22
+ # constructable :paradigms,
23
23
  # readable: true,
24
24
  # required: true,
25
- # validate: ->(value) { value.is_a?(Array) }]
25
+ # validate: ->(value) { value.is_a?(Array) }
26
26
  # end
27
27
  #
28
28
  # c = ProgrammingLanguage.new(paradigms: :functional)
@@ -36,22 +36,6 @@ module Constructable
36
36
  def constructable(*args)
37
37
  @constructor ||= Constructor.new(self)
38
38
  @constructor.define_attributes(args)
39
- case self
40
- when Class
41
- @constructor.redefine_new(self)
42
- when Module
43
- redefine_new_logic = proc do |base|
44
-
45
- base.instance_variable_set(:@constructor, @constructor)
46
- if Class == base.class
47
- @constructor.redefine_new(base)
48
- elsif Module == base.class
49
- base.define_singleton_method :included, &redefine_new_logic
50
- end
51
- end
52
-
53
- define_singleton_method :included, &redefine_new_logic
54
- end
55
39
  return nil
56
40
  end
57
41
  end
@@ -71,7 +71,7 @@ describe 'Attribute' do
71
71
  attribute.process(foo: 'notanumber')
72
72
  rescue Exception => e
73
73
  assert AttributeError === e, "[#{e.class},#{e.message}] was not expected"
74
- assert_equal ':foo needs to be of type Integer', e.message
74
+ assert_equal ':foo must be of type Integer', e.message
75
75
  else
76
76
  assert false, 'AttributeError was not raised'
77
77
  end
@@ -2,61 +2,68 @@ require 'helper'
2
2
  describe 'integration' do
3
3
  describe 'Class' do
4
4
  describe 'constructable' do
5
+ before do
6
+ @klass = Class.new
7
+ end
8
+
9
+ it 'has a nice syntax' do
10
+ @klass.constructable :foo, :bar, accessible: true, validate_type: Integer
11
+ instance = @klass.new(foo: 5, bar: 2)
12
+ assert_equal 5, instance.foo
13
+ assert_raises AttributeError do
14
+ instance.bar = '5'
15
+ end
16
+ end
17
+
5
18
  it 'should assign values found in the constructer hash' do
6
- klass = Class.new
7
- klass.constructable([:foo, readable: true])
8
- instance = klass.new(foo: :bar)
19
+ @klass.constructable(:foo, readable: true)
20
+ instance = @klass.new(foo: :bar)
9
21
  assert_equal :bar, instance.foo
10
22
  end
11
23
 
12
24
  it 'should work with inheritance' do
13
- klass = Class.new
14
- klass.constructable([:bar, readable: true])
15
- inherited_klass = Class.new(klass)
25
+ @klass.constructable(:bar, readable: true)
26
+ inherited_klass = Class.new(@klass)
16
27
  instance = inherited_klass.new(bar: 1)
17
28
  assert_equal 1, instance.bar
18
29
  end
19
30
 
20
31
  it 'should be possible to make attributes required' do
21
- klass = Class.new
22
- klass.constructable([:bar, required: true])
32
+ @klass.constructable(:bar, required: true)
23
33
  assert_raises AttributeError do
24
- klass.new
34
+ @klass.new
25
35
  end
26
36
  end
27
37
 
28
38
  describe 'should not break the initalize behaviour' do
29
39
  it 'works for methods with arguments + options provided' do
30
- klass = Class.new
31
- klass.constructable [:bar, accessible: true]
32
- klass.class_eval do
40
+ @klass.constructable :bar, accessible: true
41
+ @klass.class_eval do
33
42
  def initialize(options = {})
34
43
  self.bar = 20
35
44
  end
36
45
  end
37
- instance = klass.new(bar: 5)
46
+ instance = @klass.new(bar: 5)
38
47
  assert_equal 20, instance.bar
39
48
  end
40
49
 
41
50
  it 'works for initialize methods with arguments' do
42
- klass = Class.new
43
- klass.constructable [:bar, accessible: true]
44
- klass.send :attr_accessor, :baz
45
- klass.class_eval do
51
+ @klass.constructable :bar, accessible: true
52
+ @klass.send :attr_accessor, :baz
53
+ @klass.class_eval do
46
54
  def initialize(baz, options = {})
47
55
  self.baz = baz
48
56
  end
49
57
  end
50
58
 
51
- instance = klass.new(1, bar: 5)
59
+ instance = @klass.new(1, bar: 5)
52
60
  assert_equal 1, instance.baz
53
61
  assert_equal 5, instance.bar
54
62
  end
55
63
  end
56
64
 
57
65
  it 'should return nil' do
58
- klass = Class.new
59
- assert_equal nil, klass.constructable(:bar)
66
+ assert_equal nil, @klass.constructable(:bar)
60
67
  end
61
68
  end
62
69
  end
@@ -8,13 +8,13 @@ describe 'Constructor' do
8
8
  describe 'define_attributes' do
9
9
  it 'should define attr_accessors' do
10
10
  klass = Class.new
11
- klass.constructable([:foo, accessible: true])
11
+ klass.constructable(:foo, accessible: true)
12
12
  assert_respond_to klass.new, :foo
13
13
  assert_respond_to klass.new, :foo=
14
14
  end
15
15
 
16
16
  it 'defines public setters validating like in the constructor' do
17
- @klass.constructable [:integer, validate_type: Integer, writable: true]
17
+ @klass.constructable :integer, validate_type: Integer, writable: true
18
18
  instance = @klass.new
19
19
  assert_raises AttributeError do
20
20
  instance.integer = 6.6
@@ -25,14 +25,14 @@ describe 'Constructor' do
25
25
 
26
26
  describe 'class' do
27
27
  it 'getters ' do
28
- @klass.constructable [:integer, validate_type: Integer, accessible: true]
28
+ @klass.constructable :integer, validate_type: Integer, accessible: true
29
29
  @klass.class_eval { define_method(:integer){ 1 } }
30
30
  instance = @klass.new(integer: 2)
31
31
  assert_equal 1, instance.integer
32
32
  end
33
33
 
34
34
  it 'setters ' do
35
- @klass.constructable [:integer, validate_type: Integer, accessible: true]
35
+ @klass.constructable :integer, validate_type: Integer, accessible: true
36
36
  @klass.class_eval { def integer=(foo);@integer = 1;end }
37
37
  instance = @klass.new(integer: 4)
38
38
  instance.integer = 5
@@ -42,7 +42,7 @@ describe 'Constructor' do
42
42
 
43
43
  describe 'module' do
44
44
  before do
45
- @module.constructable [:integer, validate_type: Integer, accessible: true]
45
+ @module.constructable :integer, validate_type: Integer, accessible: true
46
46
  end
47
47
 
48
48
  it 'getters ' do
@@ -74,14 +74,14 @@ describe 'Constructor' do
74
74
 
75
75
  describe 'allows to super to the generated method' do
76
76
  it 'gets' do
77
- @klass.constructable [:integer, validate_type: Integer, accessible: true]
77
+ @klass.constructable :integer, validate_type: Integer, accessible: true
78
78
  @klass.class_eval { def integer; super ;end }
79
79
  instance = @klass.new(integer: 2)
80
80
  assert_equal 2, instance.integer
81
81
  end
82
82
 
83
83
  it 'sets' do
84
- @klass.constructable [:integer, validate_type: Integer, accessible: true]
84
+ @klass.constructable :integer, validate_type: Integer, accessible: true
85
85
  @klass.class_eval { def integer=(value); super ;end }
86
86
  instance = @klass.new(integer: 2)
87
87
  assert_raises Constructable::AttributeError do
@@ -102,21 +102,21 @@ describe 'Constructor' do
102
102
 
103
103
  describe 'permission' do
104
104
  it 'should allow writable attributes' do
105
- @klass.constructable [:writable_attribute, writable: true]
105
+ @klass.constructable :writable_attribute, writable: true
106
106
  instance = @klass.new
107
107
  instance.writable_attribute = "hello"
108
108
  assert_equal "hello", instance.instance_variable_get(:@writable_attribute)
109
109
  end
110
110
 
111
111
  it 'should allow readable attributes' do
112
- @klass.constructable [:readable_attribute, readable: true]
112
+ @klass.constructable :readable_attribute, readable: true
113
113
  instance = @klass.new
114
114
  instance.instance_variable_set(:@readable_attribute, "hello")
115
115
  assert_equal "hello", instance.readable_attribute
116
116
  end
117
117
 
118
118
  it 'should allow accessible attributes' do
119
- @klass.constructable [:accessible_attribute, accessible: true]
119
+ @klass.constructable :accessible_attribute, accessible: true
120
120
  instance = @klass.new
121
121
  instance.accessible_attribute = 'hello'
122
122
  assert_equal 'hello', instance.accessible_attribute
@@ -126,7 +126,7 @@ describe 'Constructor' do
126
126
  describe 'module support' do
127
127
  before do
128
128
  @foo = Module.new do
129
- constructable [:foo, readable: true]
129
+ constructable :foo, readable: true
130
130
  end
131
131
  end
132
132
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: constructable
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.1
5
+ version: 0.3.2
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-15 00:00:00 +02:00
13
+ date: 2011-06-17 00:00:00 +02:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -78,6 +78,7 @@ files:
78
78
  - LICENSE.txt
79
79
  - README.markdown
80
80
  - Rakefile
81
+ - TODO.md
81
82
  - VERSION
82
83
  - constructable.gemspec
83
84
  - lib/constructable.rb
@@ -103,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
104
  requirements:
104
105
  - - ">="
105
106
  - !ruby/object:Gem::Version
106
- hash: 2398714721687136545
107
+ hash: 932073679521130628
107
108
  segments:
108
109
  - 0
109
110
  version: "0"