constructor 1.0.0

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/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: