constructor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 1.0.0 / 2007-11-18
2
+
3
+ * Released!
data/Manifest.txt ADDED
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/constructor.rb
6
+ specs/constructor_spec.rb
data/README.txt ADDED
@@ -0,0 +1,77 @@
1
+ constructor
2
+
3
+ * http://rubyforge.org/projects/atomicobjectrb/
4
+ * http://atomicobjectrb.rubyforge.org/constructor
5
+
6
+ == DESCRIPTION:
7
+
8
+ Declarative means to define object properties by passing a hash
9
+ to the constructor, which will set the corresponding ivars.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Declarative constructor definition and ivar initialization
14
+
15
+ == SYNOPSIS:
16
+
17
+ require 'constructor'
18
+
19
+ class Horse
20
+ constructor :name, :breed, :weight
21
+ end
22
+ Horse.new :name => 'Ed', :breed => 'Mustang', :weight => 342
23
+
24
+ By default the ivars do not get accessors defined.
25
+ But you can get them auto-made if you want:
26
+
27
+ class Horse
28
+ constructor :name, :breed, :weight, :accessors => true
29
+ end
30
+ ...
31
+ puts my_horse.weight
32
+
33
+ Arguments specified are required by default. You can disable
34
+ strict argument checking with :strict option. This means that
35
+ the constructor will not raise an error if you pass more or
36
+ fewer arguments than declared.
37
+
38
+ class Donkey
39
+ constructor :age, :odor, :strict => false
40
+ end
41
+
42
+ ... this allows you to pass either an age or odor key (or neither)
43
+ to the Donkey constructor.
44
+
45
+
46
+ == REQUIREMENTS:
47
+
48
+ * rubygems
49
+
50
+ == INSTALL:
51
+
52
+ * sudo gem install constructor
53
+
54
+ == LICENSE:
55
+
56
+ (The MIT License)
57
+
58
+ Copyright (c) 2007 Atomic Object
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining
61
+ a copy of this software and associated documentation files (the
62
+ 'Software'), to deal in the Software without restriction, including
63
+ without limitation the rights to use, copy, modify, merge, publish,
64
+ distribute, sublicense, and/or sell copies of the Software, and to
65
+ permit persons to whom the Software is furnished to do so, subject to
66
+ the following conditions:
67
+
68
+ The above copyright notice and this permission notice shall be
69
+ included in all copies or substantial portions of the Software.
70
+
71
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
72
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
74
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
75
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
76
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
77
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/constructor.rb'
4
+ require 'spec/rake/spectask'
5
+
6
+
7
+ desc 'Default: run specs'
8
+ task :default => :spec
9
+ Hoe.new('constructor', CONSTRUCTOR_VERSION) do |p|
10
+ p.rubyforge_name = 'atomicobjectrb'
11
+ p.author = 'Atomic Object'
12
+ p.email = 'dev@atomicobject.com'
13
+ p.summary = 'Declarative, named constructor arguments.'
14
+ p.description = p.paragraphs_of('README.txt', 2).join("\n\n")
15
+ p.url = p.paragraphs_of('README.txt', 1).first.gsub(/\* /,'').split(/\n/)
16
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
17
+ end
18
+
19
+ desc 'Run constructor specs'
20
+ Spec::Rake::SpecTask.new(:spec) do |t|
21
+ t.spec_files = FileList['specs/*_spec.rb']
22
+ t.spec_opts << '-c -f s'
23
+ end
@@ -0,0 +1,106 @@
1
+ CONSTRUCTOR_VERSION = '1.0.0'
2
+
3
+ class Class
4
+ def constructor(*attrs)
5
+ # Look for embedded options in the listing:
6
+ opts = attrs.find { |a| a.kind_of?(Hash) and attrs.delete(a) }
7
+ do_acc = opts.nil? ? false : opts[:accessors] == true
8
+ require_args = opts.nil? ? true : opts[:strict] != false
9
+ super_args = opts.nil? ? nil : opts[:super]
10
+
11
+ # Incorporate superclass's constructor keys, if our superclass
12
+ if superclass.constructor_keys
13
+ attrs = [attrs,superclass.constructor_keys].flatten
14
+ end
15
+ # Generate ivar assigner code lines
16
+ assigns = ''
17
+ attrs.each do |k|
18
+ assigns += "@#{k.to_s} = args[:#{k.to_s}]\n"
19
+ end
20
+
21
+ # If accessors option is on, declare accessors for the attributes:
22
+ if do_acc
23
+ self.class_eval "attr_accessor " + attrs.map {|x| ":#{x.to_s}"}.join(',')
24
+ end
25
+
26
+ # If user supplied super-constructor hints:
27
+ super_call = ''
28
+ if super_args
29
+ list = super_args.map do |a|
30
+ case a
31
+ when String
32
+ %|"#{a}"|
33
+ when Symbol
34
+ %|:#{a}|
35
+ end
36
+ end
37
+ super_call = %|super(#{list.join(',')})|
38
+ end
39
+
40
+ # If strict is on, define the constructor argument validator method,
41
+ # and setup the initializer to invoke the validator method.
42
+ # Otherwise, insert lax code into the initializer.
43
+ validation_code = "return if args.nil?"
44
+ if require_args
45
+ self.class_eval do
46
+ def _validate_constructor_args(args)
47
+ # First, make sure we've got args of some kind
48
+ unless args and args.keys and args.keys.size > 0
49
+ raise ConstructorArgumentError.new(self.class.constructor_keys)
50
+ end
51
+ # Scan for missing keys in the argument hash
52
+ a_keys = args.keys
53
+ missing = []
54
+ self.class.constructor_keys.each do |ck|
55
+ unless a_keys.member?(ck)
56
+ missing << ck
57
+ end
58
+ a_keys.delete(ck) # Delete inbound keys as we address them
59
+ end
60
+ if missing.size > 0 || a_keys.size > 0
61
+ raise ConstructorArgumentError.new(missing,a_keys)
62
+ end
63
+ end
64
+ end
65
+ # Setup the code to insert into the initializer:
66
+ validation_code = "_validate_constructor_args args "
67
+ end
68
+
69
+ # Generate the initializer code
70
+ self.class_eval %{
71
+ def initialize(args=nil)
72
+ #{super_call}
73
+ #{validation_code}
74
+ #{assigns}
75
+ setup if respond_to?(:setup)
76
+ end
77
+ }
78
+
79
+ # Remember our constructor keys
80
+ @_ctor_keys = attrs
81
+ end
82
+
83
+ # Access the constructor keys for this class
84
+ def constructor_keys; @_ctor_keys; end
85
+ end
86
+
87
+ # Fancy validation exception, based on missing and extraneous keys.
88
+ class ConstructorArgumentError < RuntimeError
89
+ def initialize(missing,rejected=[])
90
+ err_msg = ''
91
+ if missing.size > 0
92
+ err_msg = "Missing constructor args [#{missing.join(',')}]"
93
+ end
94
+ if rejected.size > 0
95
+ # Some inbound keys were not addressed earlier; this means they're unwanted
96
+ if err_msg
97
+ err_msg << "; " # Appending to earlier message about missing items
98
+ else
99
+ err_msg = ''
100
+ end
101
+ # Enumerate the rejected key names
102
+ err_msg << "Rejected constructor args [#{rejected.join(',')}]"
103
+ end
104
+ super err_msg
105
+ end
106
+ end
@@ -0,0 +1,319 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/constructor')
2
+
3
+ class TestingClass
4
+ attr_accessor :foo, :bar, :why, :qux
5
+ constructor :foo, :bar, :why, :qux, :strict => false
6
+
7
+ def to_pretty_pretty
8
+ "#{@foo} #{@bar}"
9
+ end
10
+
11
+ end
12
+
13
+ class Mama
14
+ attr_accessor :fat, :age
15
+ constructor :age, :strict => false
16
+ def setup
17
+ @fat = "much"
18
+ end
19
+ end
20
+
21
+ class Baby < Mama
22
+ constructor :cuteness
23
+ end
24
+
25
+ class Sissy < Mama
26
+ attr_accessor :friends, :beauty
27
+ constructor :beauty, :strict => false
28
+ def setup
29
+ super #IMPORTANT!
30
+ @friends = "many"
31
+ end
32
+ end
33
+
34
+ class TestingStrictArgsDefault
35
+ constructor :foo, :bar
36
+ def to_pretty_pretty
37
+ "#{@foo} #{@bar}"
38
+ end
39
+ end
40
+
41
+ class TestingStrictArgs
42
+ constructor :foo, :bar, :strict => true
43
+ def to_pretty_pretty
44
+ "#{@foo} #{@bar}"
45
+ end
46
+ end
47
+
48
+ class TestingStrictArgs2
49
+ constructor :foo, :bar, :accessors => true
50
+ end
51
+
52
+ class SubclassOfTestingClass < TestingClass
53
+ end
54
+
55
+ class SubclassOfTestingClass2 < TestingClass
56
+ def initialize; end
57
+ end
58
+
59
+ class SubclassOfTestingClass3 < TestingClass
60
+ attr_reader :my_new_var
61
+ def initialize(hash = nil)
62
+ super
63
+ @my_new_var = "something"
64
+ end
65
+ end
66
+
67
+ class TestingAutoAccessors
68
+ constructor :foo, :bar, :why, :qux, :accessors => true, :strict => false
69
+ def to_pretty_pretty
70
+ "#{@foo} #{@bar}"
71
+ end
72
+ end
73
+
74
+ class TestingSuperConstructorBase
75
+ attr_reader :a, :b
76
+ def initialize(a,b)
77
+ @a = a
78
+ @b = b
79
+ end
80
+ end
81
+
82
+ class TestingSuperConstructor < TestingSuperConstructorBase
83
+ constructor :far, :away, :accessors => true, :super => ["once", :twice], :strict => false
84
+ end
85
+
86
+ class TestingSuperConstructorBase2
87
+ attr_reader :c, :d
88
+ def initialize
89
+ @c = 'what a'
90
+ @d = 'day for'
91
+ end
92
+ end
93
+
94
+ class TestingSuperConstructor2 < TestingSuperConstructorBase2
95
+ constructor :some, :accessors => true, :super => [], :strict => false
96
+ end
97
+
98
+ class TestingBlockedAccessors
99
+ constructor :foo, :bar, :accessors => false
100
+ def to_pretty_pretty
101
+ "#{@foo} #{@bar}"
102
+ end
103
+ end
104
+
105
+ class Papa
106
+ constructor :car, :saw
107
+ end
108
+
109
+ class Sonny < Papa
110
+ constructor :computer, :accessors => true
111
+ end
112
+
113
+ class Llamma
114
+ attr_accessor :hungry, :hair
115
+ constructor :hair
116
+ def setup
117
+ @hungry = true
118
+ end
119
+ end
120
+
121
+ describe 'standard constructor usage' do
122
+ it 'allows for object construction using a hash of named arguments' do
123
+ fuh = TestingClass.new(
124
+ :foo => 'my foo',
125
+ :bar => 'my bar',
126
+ :qux => 'my qux',
127
+ :why => 'lucky'
128
+ )
129
+
130
+ fuh.foo.should eql('my foo')
131
+ fuh.bar.should eql('my bar')
132
+ fuh.qux.should eql('my qux')
133
+ fuh.why.should eql('lucky')
134
+ fuh.to_pretty_pretty.should eql('my foo my bar')
135
+ end
136
+
137
+ it 'calls setup method if defined' do
138
+ ralph = Llamma.new :hair => 'red'
139
+ ralph.hungry.should be_true
140
+ ralph.hair.should eql('red')
141
+ end
142
+ end
143
+
144
+ describe "constructor's accessor option" do
145
+ it 'provides accessors for constructor arguments when accessor option is true' do
146
+ fuh = TestingAutoAccessors.new(
147
+ :foo => 'my foo',
148
+ :bar => 'my bar',
149
+ :qux => 'my qux',
150
+ :why => 'lucky'
151
+ )
152
+ fuh.foo.should eql('my foo')
153
+ fuh.bar.should eql('my bar')
154
+ fuh.qux.should eql('my qux')
155
+ fuh.why.should eql('lucky')
156
+ fuh.to_pretty_pretty.should eql('my foo my bar')
157
+ end
158
+
159
+ it 'does not provide accessors for constructor arguments when accessor option is false' do
160
+ fuh = TestingBlockedAccessors.new :foo => 'my foo', :bar => 'my bar'
161
+ lambda {fuh.foo}.should raise_error(NoMethodError)
162
+ lambda {fuh.bar}.should raise_error(NoMethodError)
163
+ fuh.to_pretty_pretty.should eql('my foo my bar')
164
+ end
165
+ end
166
+
167
+ describe 'using constructor with inheritance' do
168
+ it 'allows for inheritance of constructor arguments using a non-constructor defined subclass' do
169
+ fuh = SubclassOfTestingClass.new :foo => 'whu?'
170
+ fuh.foo.should eql('whu?')
171
+ fuh.bar.should be_nil
172
+ fuh.qux.should be_nil
173
+ fuh.why.should be_nil
174
+ end
175
+
176
+ it 'allows for standard construction of a non-constructor subclass of a non-strict constuctor superclass' do
177
+ fuh = SubclassOfTestingClass2.new
178
+ fuh.foo.should be_nil
179
+ end
180
+
181
+ it 'runs initialize method of a sublcass' do
182
+ fuh = SubclassOfTestingClass3.new
183
+ fuh.my_new_var.should eql('something')
184
+ fuh.foo.should be_nil
185
+ fuh.bar.should be_nil
186
+ fuh.qux.should be_nil
187
+ fuh.why.should be_nil
188
+ end
189
+
190
+ it 'passes named constructor args to superclass when subclass calls super' do
191
+ fuh = SubclassOfTestingClass3.new :foo => 12
192
+ fuh.my_new_var.should eql('something')
193
+ fuh.foo.should eql(12)
194
+ fuh.bar.should be_nil
195
+ fuh.qux.should be_nil
196
+ fuh.why.should be_nil
197
+ end
198
+
199
+ it 'allows for inheritance of constructor arguments using a constructor defined subclass' do
200
+ s = Sonny.new :car => 'Nissan', :saw => 'Dewalt', :computer => 'Dell'
201
+ s.computer.should eql('Dell')
202
+ s.saw.should eql('Dewalt')
203
+ s.car.should eql('Nissan')
204
+ end
205
+
206
+ it 'calls the setup method on superclass if subclass does not define a setup method' do
207
+ baby = Baby.new :cuteness => 'little', :age => 1
208
+ baby.fat.should eql('much')
209
+ end
210
+
211
+ it 'calls parent class setup when super is called from subclass setup' do
212
+ m = Mama.new :age => 55
213
+ m.age.should eql(55)
214
+ m.fat.should eql('much')
215
+
216
+ s = Sissy.new :age => 19, :beauty => 'medium', :fat => 'yeah'
217
+ s.age.should eql(19)
218
+ s.beauty.should eql('medium')
219
+ s.fat.should eql('much')
220
+ s.friends.should eql('many')
221
+ end
222
+
223
+ it 'passes arguments given in the super option to the initializer of a non-constructor defined superclass' do
224
+ tsc = TestingSuperConstructor.new(:far => 'oo', :away => 'kk')
225
+ tsc.far.should eql('oo')
226
+ tsc.away.should eql('kk')
227
+ tsc.a.should eql("once")
228
+ tsc.b.should eql(:twice)
229
+ end
230
+
231
+ it 'calls non-constructor defined superclass constructor when the super option is an empty array' do
232
+ tsc = TestingSuperConstructor2.new(:some => 'thing')
233
+ tsc.some.should eql('thing')
234
+ tsc.c.should eql('what a')
235
+ tsc.d.should eql('day for')
236
+ end
237
+ end
238
+
239
+ describe 'stict mode usage' do
240
+ it 'allows omission of arguments when strict is off' do
241
+ fuh = TestingClass.new :foo => 'my foo'
242
+
243
+ fuh.foo.should eql('my foo')
244
+ fuh.bar.should be_nil
245
+ fuh.qux.should be_nil
246
+ fuh.why.should be_nil
247
+ end
248
+
249
+ it 'allows no arguments to a constructor when strict is off' do
250
+ fuh = TestingClass.new
251
+ fuh.foo.should be_nil
252
+ fuh.bar.should be_nil
253
+ fuh.qux.should be_nil
254
+ fuh.why.should be_nil
255
+ end
256
+
257
+ it 'does not interfere with normal object construction' do
258
+ require 'rexml/document'
259
+ d = REXML::Document.new '<base/>'
260
+ d.should_not be_nil
261
+ d.root.name.should eql('base')
262
+ end
263
+
264
+ def see_strict_args_in_effect_for(clazz)
265
+ fuh = clazz.new :foo => 'my foo', :bar => 'my bar'
266
+ fuh.to_pretty_pretty.should eql('my foo my bar')
267
+
268
+ # Omit foo
269
+ lambda {
270
+ TestingStrictArgsDefault.new :bar => 'ok,yeah'
271
+ }.should raise_error(ConstructorArgumentError, /foo/)
272
+
273
+ # Omit bar
274
+ lambda {
275
+ TestingStrictArgsDefault.new :foo => 'ok,yeah'
276
+ }.should raise_error(ConstructorArgumentError, /bar/)
277
+ end
278
+
279
+ it 'defaults to strict argument enforcement' do
280
+ see_strict_args_in_effect_for TestingStrictArgsDefault
281
+ end
282
+
283
+ it 'enforces strict arguments when strict option is true' do
284
+ see_strict_args_in_effect_for TestingStrictArgs
285
+ end
286
+
287
+ it 'does not allow empty constructor arguments when strict option is true' do
288
+ lambda {TestingStrictArgs.new {}}.should raise_error(ConstructorArgumentError,/foo,bar/)
289
+ lambda {TestingStrictArgs.new}.should raise_error(ConstructorArgumentError,/foo,bar/)
290
+ lambda {TestingStrictArgs.new nil}.should raise_error(ConstructorArgumentError,/foo,bar/)
291
+ end
292
+
293
+ it 'does not allow extraneous arguments when strict option is true' do
294
+ lambda {
295
+ TestingStrictArgs.new :foo => 1, :bar => 2, :other => 3, :thing => 4
296
+ }.should raise_error(ConstructorArgumentError,/thing,other/)
297
+ end
298
+
299
+ it 'allows for setting accessors option while in strict mode' do
300
+ t2 = TestingStrictArgs2.new :foo => 1, :bar => 2
301
+
302
+ # See that accessors work
303
+ t2.foo.should eql(1)
304
+ t2.bar.should eql(2)
305
+
306
+ # See that strictness still applies
307
+ lambda {TestingStrictArgs2.new :no => 'good'}.should raise_error(ConstructorArgumentError)
308
+ end
309
+ end
310
+
311
+ describe 'catching ConstructorArgumentError' do
312
+ it 'allows for generic rescuing of constructor argument errors' do
313
+ begin
314
+ TestingStrictArgs.new :broken => 'yoobetcha'
315
+ rescue => bad_news
316
+ bad_news.should be_kind_of(ConstructorArgumentError)
317
+ end
318
+ end
319
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: constructor
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2007-11-18 00:00:00 -05:00
8
+ summary: Declarative, named constructor arguments.
9
+ require_paths:
10
+ - lib
11
+ email: dev@atomicobject.com
12
+ homepage: http://rubyforge.org/projects/atomicobjectrb/
13
+ rubyforge_project: atomicobjectrb
14
+ description: "== DESCRIPTION: Declarative means to define object properties by passing a hash to the constructor, which will set the corresponding ivars."
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Atomic Object
31
+ files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ - Rakefile
36
+ - lib/constructor.rb
37
+ - specs/constructor_spec.rb
38
+ test_files: []
39
+
40
+ rdoc_options:
41
+ - --main
42
+ - README.txt
43
+ extra_rdoc_files:
44
+ - History.txt
45
+ - Manifest.txt
46
+ - README.txt
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ requirements: []
52
+
53
+ dependencies:
54
+ - !ruby/object:Gem::Dependency
55
+ name: hoe
56
+ version_requirement:
57
+ version_requirements: !ruby/object:Gem::Version::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.0
62
+ version: