doodle 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +24 -0
- data/Manifest.txt +26 -1
- data/README.txt +9 -8
- data/lib/doodle.rb +43 -1496
- data/lib/doodle/app.rb +6 -0
- data/lib/doodle/attribute.rb +165 -0
- data/lib/doodle/base.rb +180 -0
- data/lib/doodle/collector-1.9.rb +72 -0
- data/lib/doodle/collector.rb +191 -0
- data/lib/doodle/comparable.rb +8 -0
- data/lib/doodle/conversion.rb +80 -0
- data/lib/doodle/core.rb +42 -0
- data/lib/doodle/datatype-holder.rb +39 -0
- data/lib/doodle/debug.rb +20 -0
- data/lib/doodle/deferred.rb +13 -0
- data/lib/doodle/equality.rb +21 -0
- data/lib/doodle/exceptions.rb +29 -0
- data/lib/doodle/factory.rb +91 -0
- data/lib/doodle/getter-setter.rb +154 -0
- data/lib/doodle/info.rb +298 -0
- data/lib/doodle/inherit.rb +40 -0
- data/lib/doodle/json.rb +38 -0
- data/lib/doodle/marshal.rb +16 -0
- data/lib/doodle/normalized_array.rb +512 -0
- data/lib/doodle/normalized_hash.rb +356 -0
- data/lib/doodle/ordered-hash.rb +8 -0
- data/lib/doodle/singleton.rb +23 -0
- data/lib/doodle/smoke-and-mirrors.rb +23 -0
- data/lib/doodle/to_hash.rb +17 -0
- data/lib/doodle/utils.rb +173 -11
- data/lib/doodle/validation.rb +122 -0
- data/lib/doodle/version.rb +1 -1
- data/lib/molic_orderedhash.rb +24 -10
- data/spec/assigned_spec.rb +45 -0
- data/spec/attributes_spec.rb +7 -7
- data/spec/collector_spec.rb +100 -13
- data/spec/doodle_context_spec.rb +5 -5
- data/spec/from_spec.rb +43 -3
- data/spec/json_spec.rb +232 -0
- data/spec/member_init_spec.rb +11 -11
- data/spec/modules_spec.rb +4 -4
- data/spec/multi_collector_spec.rb +91 -0
- data/spec/must_spec.rb +32 -0
- data/spec/spec_helper.rb +14 -4
- data/spec/specialized_attribute_class_spec.rb +2 -2
- data/spec/typed_collector_spec.rb +57 -0
- data/spec/xml_spec.rb +8 -8
- metadata +33 -3
@@ -0,0 +1,122 @@
|
|
1
|
+
class Doodle
|
2
|
+
# A Validation represents a validation rule applied to the instance
|
3
|
+
# after initialization. Generated using the Doodle::BaseMethods#must directive.
|
4
|
+
class Validation
|
5
|
+
attr_accessor :message
|
6
|
+
attr_accessor :block
|
7
|
+
# create a new validation rule. This is typically a result of
|
8
|
+
# calling +must+ so the text should work following the word
|
9
|
+
# "must", e.g. "must not be nil", "must be >= 10", etc.
|
10
|
+
def initialize(message = 'not be nil', &block)
|
11
|
+
@message = message
|
12
|
+
@block = block_given? ? block : proc { |x| !self.nil? }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ValidationHelper
|
17
|
+
# add a validation
|
18
|
+
def must(constraint = 'be valid', &block)
|
19
|
+
__doodle__.local_validations << Validation.new(constraint, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# add a validation that attribute must be of class <= kind
|
23
|
+
def kind(*args, &block)
|
24
|
+
if args.size > 0
|
25
|
+
@kind = [args].flatten
|
26
|
+
# todo[figure out how to handle kind being specified twice?]
|
27
|
+
if @kind.size > 2
|
28
|
+
kind_text = "be a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # =>
|
29
|
+
else
|
30
|
+
kind_text = "be a kind of #{@kind.to_s}"
|
31
|
+
end
|
32
|
+
__doodle__.local_validations << (Validation.new(kind_text) { |x| @kind.any? { |klass| x.kind_of?(klass) } })
|
33
|
+
else
|
34
|
+
@kind ||= []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# validate that individual attribute args meet rules defined with +must+
|
39
|
+
# fixme: move
|
40
|
+
def validate(owner, *args)
|
41
|
+
##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
42
|
+
#p [:validate, 1, args]
|
43
|
+
begin
|
44
|
+
value = convert(owner, *args)
|
45
|
+
rescue Exception => e
|
46
|
+
owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } - #{e.message}", Doodle::Utils.doodle_caller
|
47
|
+
end
|
48
|
+
#
|
49
|
+
# Note to self: these validations are not affected by
|
50
|
+
# doodle.validation_on because they are for ~individual
|
51
|
+
# attributes~ - validation_on is for the ~object as a whole~ -
|
52
|
+
# so don't futz with this again :)
|
53
|
+
#
|
54
|
+
# p [:validate, 2, args, :becomes, value]
|
55
|
+
__doodle__.validations.each do |v|
|
56
|
+
##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
|
57
|
+
if !v.block[value]
|
58
|
+
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", Doodle::Utils.doodle_caller
|
59
|
+
end
|
60
|
+
end
|
61
|
+
#p [:validate, 3, value]
|
62
|
+
value
|
63
|
+
end
|
64
|
+
|
65
|
+
# validate this object by applying all validations in sequence
|
66
|
+
#
|
67
|
+
# - if all == true, validate all attributes, e.g. when loaded from
|
68
|
+
# YAML, else validate at object level only
|
69
|
+
#
|
70
|
+
def validate!(all = true)
|
71
|
+
##DBG: Doodle::Debug.d { [:validate!, all, caller] }
|
72
|
+
if all
|
73
|
+
__doodle__.errors.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
# first check that individual attributes are valid
|
77
|
+
|
78
|
+
if __doodle__.validation_on
|
79
|
+
if self.class == Class
|
80
|
+
attribs = __doodle__.class_attributes
|
81
|
+
##DBG: Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
|
82
|
+
else
|
83
|
+
attribs = __doodle__.attributes
|
84
|
+
##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes", doodle.attributes] }
|
85
|
+
end
|
86
|
+
attribs.each do |name, att|
|
87
|
+
if ivar_defined?(name)
|
88
|
+
# if all == true, reset values so conversions and
|
89
|
+
# validations are applied to raw instance variables
|
90
|
+
# e.g. when loaded from YAML
|
91
|
+
if all && !att.readonly
|
92
|
+
##DBG: Doodle::Debug.d { [:validate!, :sending, att.name, instance_variable_get(ivar_name) ] }
|
93
|
+
__send__("#{name}=", ivar_get(name))
|
94
|
+
end
|
95
|
+
elsif att.optional? # treat default/init as special case
|
96
|
+
##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
|
97
|
+
next
|
98
|
+
elsif self.class != Class
|
99
|
+
__doodle__.handle_error name, Doodle::ValidationError, "#{self.kind_of?(Class) ? self : self.class } missing required attribute '#{name}'", Doodle::Utils.doodle_caller
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# now apply whole object level validations
|
104
|
+
|
105
|
+
##DBG: Doodle::Debug.d { [:validate!, "validations", doodle_validations ]}
|
106
|
+
__doodle__.validations.each do |v|
|
107
|
+
##DBG: Doodle::Debug.d { [:validate!, self, v ] }
|
108
|
+
begin
|
109
|
+
if !instance_eval(&v.block)
|
110
|
+
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", Doodle::Utils.doodle_caller
|
111
|
+
end
|
112
|
+
rescue Exception => e
|
113
|
+
__doodle__.handle_error self, ValidationError, e.to_s, Doodle::Utils.doodle_caller
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# if OK, then return self
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
data/lib/doodle/version.rb
CHANGED
data/lib/molic_orderedhash.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
# AUTHOR
|
2
|
-
# jan molic /mig/at/1984/dot/cz/
|
3
|
-
#
|
4
|
-
# DESCRIPTION
|
5
|
-
# Hash with preserved order and some array-like extensions
|
6
|
-
# Public domain.
|
7
|
-
#
|
8
|
-
# THANKS
|
9
|
-
# Andrew Johnson for his suggestions and fixes of Hash[],
|
10
|
-
# merge, to_a, inspect and shift
|
11
1
|
class Doodle
|
2
|
+
# AUTHOR
|
3
|
+
# jan molic /mig/at/1984/dot/cz/
|
4
|
+
#
|
5
|
+
# DESCRIPTION
|
6
|
+
# Hash with preserved order and some array-like extensions
|
7
|
+
# Public domain.
|
8
|
+
#
|
9
|
+
# THANKS
|
10
|
+
# Andrew Johnson for his suggestions and fixes of Hash[],
|
11
|
+
# merge, to_a, inspect and shift
|
12
12
|
class OrderedHash < ::Hash
|
13
13
|
attr_accessor :order
|
14
14
|
|
@@ -29,6 +29,16 @@ class Doodle
|
|
29
29
|
super
|
30
30
|
@order = []
|
31
31
|
end
|
32
|
+
def clone
|
33
|
+
c = super
|
34
|
+
c.order = order.clone
|
35
|
+
c
|
36
|
+
end
|
37
|
+
def dup
|
38
|
+
c = super
|
39
|
+
c.order = order.dup
|
40
|
+
c
|
41
|
+
end
|
32
42
|
def store_only(a,b)
|
33
43
|
store a,b
|
34
44
|
end
|
@@ -39,9 +49,13 @@ class Doodle
|
|
39
49
|
end
|
40
50
|
alias []= store
|
41
51
|
def ==(hsh2)
|
52
|
+
return false if !hsh2.respond_to?(:order)
|
42
53
|
return false if @order != hsh2.order
|
43
54
|
super hsh2
|
44
55
|
end
|
56
|
+
# def eql?(hsh2)
|
57
|
+
# super hsh2
|
58
|
+
# end
|
45
59
|
def clear
|
46
60
|
@order = []
|
47
61
|
super
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Doodle, 'assigned? with default' do
|
4
|
+
temporary_constant :Foo do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
class Foo < Doodle
|
8
|
+
has :name, :default => nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should return false if attribute not assigned' do
|
13
|
+
foo = Foo.new
|
14
|
+
foo.assigned?(:name).should_be false
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return true if attribute assigned' do
|
18
|
+
foo = Foo.new('foo')
|
19
|
+
foo.assigned?(:name).should_be true
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Doodle, 'assigned? with init' do
|
26
|
+
temporary_constant :Foo do
|
27
|
+
|
28
|
+
before :each do
|
29
|
+
class Foo < Doodle
|
30
|
+
has :name, :init => ""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should return true if attribute has init even when not specifically assigned' do
|
35
|
+
foo = Foo.new
|
36
|
+
foo.assigned?(:name).should_be true
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should return true if attribute has init and has been assigned' do
|
40
|
+
foo = Foo.new('foo')
|
41
|
+
foo.assigned?(:name).should_be true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/spec/attributes_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe Doodle::DoodleAttribute, 'basics' do
|
|
16
16
|
has :cvar2
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
@foo = Foo.new
|
21
21
|
class << @foo
|
22
22
|
has :svar1
|
@@ -31,7 +31,7 @@ describe Doodle::DoodleAttribute, 'basics' do
|
|
31
31
|
@bar = nil
|
32
32
|
@foo = nil
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
it 'should have attribute :ivar1 with default defined' do
|
36
36
|
@foo.doodle.attributes[:ivar1].default.should == 'Hello'
|
37
37
|
end
|
@@ -68,7 +68,7 @@ describe Doodle::DoodleAttribute, 'basics' do
|
|
68
68
|
# expected_doodle.parents = RUBY_VERSION <= "1.8.6" ? [Foo, Object] : [Foo, Object, BasicObject]
|
69
69
|
# Bar.doodle.parents.should == expected_doodle.parents
|
70
70
|
# end
|
71
|
-
|
71
|
+
|
72
72
|
it "should have Bar's singleton doodle.parents in reverse order of definition" do
|
73
73
|
@bar.singleton_class.doodle.parents.should == []
|
74
74
|
end
|
@@ -84,20 +84,20 @@ describe Doodle::DoodleAttribute, 'basics' do
|
|
84
84
|
it 'should have inherited class_attributes in order of definition' do
|
85
85
|
@bar.doodle.class_attributes.keys.should == [:cvar1, :cvar2]
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
it 'should have local class attributes in order of definition' do
|
89
89
|
Bar.singleton_class.doodle.attributes(false).keys.should == [:cvar2]
|
90
90
|
end
|
91
91
|
|
92
92
|
# bit iffy this test - testing implementation, not interface
|
93
93
|
it 'should not inherit singleton doodle.local_attributes' do
|
94
|
-
@bar.singleton_class.class_eval { doodle.
|
94
|
+
@bar.singleton_class.class_eval { doodle.send(:collect_inherited, :local_attributes).map { |x| x[0]} }.should == []
|
95
95
|
end
|
96
96
|
|
97
97
|
it 'should not inherit singleton attributes#1' do
|
98
98
|
@bar.singleton_class.doodle.attributes.map { |x| x[0]} .should == [:svar2]
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
it 'should not inherit singleton attributes#2' do
|
102
102
|
@bar.singleton_class.doodle.attributes.keys.should == [:svar2]
|
103
103
|
end
|
@@ -128,7 +128,7 @@ describe Doodle::DoodleAttribute, 'attribute order' do
|
|
128
128
|
has :c
|
129
129
|
end
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
# it 'should keep order of inherited attributes' do
|
133
133
|
# expected_doodle.parents = RUBY_VERSION <= "1.8.6" ? [B, A, Doodle, Object] : [B, A, Doodle, Object, BasicObject]
|
134
134
|
# C.doodle.parents.should == expected_doodle.parents
|
data/spec/collector_spec.rb
CHANGED
@@ -113,12 +113,12 @@ describe Doodle, "typed collector with specified collector name" do
|
|
113
113
|
end
|
114
114
|
it "should collect items into attribute :list" do
|
115
115
|
event = nil
|
116
|
-
|
116
|
+
no_error {
|
117
117
|
event = Event do
|
118
118
|
place "Stage 1"
|
119
119
|
place "Stage 2"
|
120
120
|
end
|
121
|
-
}
|
121
|
+
}
|
122
122
|
event.locations.map{|loc| loc.name}.should_be ["Stage 1", "Stage 2"]
|
123
123
|
event.locations.map{|loc| loc.class}.should_be [::Location, ::Location]
|
124
124
|
end
|
@@ -150,9 +150,9 @@ describe Doodle, "typed collector with specified collector name initialized from
|
|
150
150
|
:locations =>
|
151
151
|
[ { :name => 'Backstage' } ] } ] }, { :name => "Stage 2" } ] }
|
152
152
|
# note: wierd formatting above simply to pass coverage
|
153
|
-
|
153
|
+
no_error {
|
154
154
|
event = Event(data)
|
155
|
-
}
|
155
|
+
}
|
156
156
|
event.locations.map{|loc| loc.name}.should_be ["Stage 1", "Stage 2"]
|
157
157
|
event.locations.map{|loc| loc.class}.should_be [::Location, ::Location]
|
158
158
|
event.locations[0].events[0].kind_of?(Event).should_be true
|
@@ -180,7 +180,8 @@ describe Doodle, "Simple keyed collector" do
|
|
180
180
|
end
|
181
181
|
|
182
182
|
it "should collect items into attribute :list" do
|
183
|
-
@foo.list.should_be(
|
183
|
+
# @foo.list.should_be( Doodle::OrderedHash[5, "World"] )
|
184
|
+
@foo.list.to_a.flatten.should_be( [5, "World"] )
|
184
185
|
end
|
185
186
|
|
186
187
|
end
|
@@ -208,7 +209,7 @@ describe Doodle, "Simple keyed collector #2" do
|
|
208
209
|
item "Hello"
|
209
210
|
item "World"
|
210
211
|
end
|
211
|
-
foo.list.to_a.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
212
|
+
foo.list.to_a.sort.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
212
213
|
end
|
213
214
|
|
214
215
|
it "should collect keyword argument enumerable into attribute :list" do
|
@@ -218,7 +219,7 @@ describe Doodle, "Simple keyed collector #2" do
|
|
218
219
|
{ :name => "World" }
|
219
220
|
]
|
220
221
|
)
|
221
|
-
foo.list.to_a.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
222
|
+
foo.list.to_a.sort.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
222
223
|
end
|
223
224
|
|
224
225
|
it "should collect positional argument enumerable into attribute :list" do
|
@@ -227,7 +228,7 @@ describe Doodle, "Simple keyed collector #2" do
|
|
227
228
|
{ :name => "World" }
|
228
229
|
]
|
229
230
|
)
|
230
|
-
foo.list.to_a.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
231
|
+
foo.list.to_a.sort.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
231
232
|
end
|
232
233
|
|
233
234
|
it "should collect named argument hash into attribute :list" do
|
@@ -236,7 +237,7 @@ describe Doodle, "Simple keyed collector #2" do
|
|
236
237
|
"World" => { :name => "World" }
|
237
238
|
}
|
238
239
|
)
|
239
|
-
foo.list.to_a.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
240
|
+
foo.list.to_a.sort.map{ |k, v| [k, v.class, v.name] }.should_be( [["Hello", Item, "Hello"], ["World", Item, "World"]] )
|
240
241
|
end
|
241
242
|
|
242
243
|
end
|
@@ -255,12 +256,12 @@ describe Doodle, 'using String as collector' do
|
|
255
256
|
end
|
256
257
|
|
257
258
|
it 'should not raise an exception' do
|
258
|
-
|
259
|
+
no_error {
|
259
260
|
text = Text do
|
260
261
|
line "line 1"
|
261
262
|
line "line 2"
|
262
263
|
end
|
263
|
-
}
|
264
|
+
}
|
264
265
|
end
|
265
266
|
|
266
267
|
it 'should concatenate strings' do
|
@@ -291,11 +292,11 @@ describe Doodle, 'collecting text values into non-String collector' do
|
|
291
292
|
end
|
292
293
|
|
293
294
|
it 'should not raise an exception' do
|
294
|
-
|
295
|
+
no_error {
|
295
296
|
signed_by = SignedBy do
|
296
297
|
signature "Sean"
|
297
298
|
end
|
298
|
-
}
|
299
|
+
}
|
299
300
|
end
|
300
301
|
|
301
302
|
it 'should convert String values to instances of collector class' do
|
@@ -308,3 +309,89 @@ describe Doodle, 'collecting text values into non-String collector' do
|
|
308
309
|
end
|
309
310
|
end
|
310
311
|
end
|
312
|
+
|
313
|
+
describe Doodle, ':collect' do
|
314
|
+
temporary_constants :ItemList, :Item do
|
315
|
+
|
316
|
+
before :each do
|
317
|
+
class ::Item < Doodle
|
318
|
+
has :title, :kind => String
|
319
|
+
end
|
320
|
+
class ItemList < Doodle
|
321
|
+
has :items, :collect => Item
|
322
|
+
# TODO: add warning/exception if collection name same as item name
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'should allow adding items of specified type' do
|
327
|
+
no_error {
|
328
|
+
list = ItemList do
|
329
|
+
item Item("one")
|
330
|
+
item Item("two")
|
331
|
+
end
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'should allow adding items of specified type via implicit type constructor' do
|
336
|
+
no_error {
|
337
|
+
list = ItemList do
|
338
|
+
item "one"
|
339
|
+
item "two"
|
340
|
+
end
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'should restrict collected items to specified type' do
|
345
|
+
expect_error(Doodle::ValidationError) {
|
346
|
+
list = ItemList do
|
347
|
+
item Date.new
|
348
|
+
end
|
349
|
+
}
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe Doodle, ':collect' do
|
356
|
+
temporary_constants :Canvas, :Shape, :Circle, :Square do
|
357
|
+
before :each do
|
358
|
+
class ::Shape < Doodle
|
359
|
+
has :x
|
360
|
+
has :y
|
361
|
+
end
|
362
|
+
class ::Circle < Shape
|
363
|
+
has :radius
|
364
|
+
end
|
365
|
+
class ::Square < Shape
|
366
|
+
has :size
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'should accept an array of types' do
|
371
|
+
class ::Canvas < Doodle
|
372
|
+
has :shapes, :collect => [Circle, Square]
|
373
|
+
end
|
374
|
+
canvas = Canvas do
|
375
|
+
circle 10,10,5
|
376
|
+
square 20,30,40
|
377
|
+
end
|
378
|
+
canvas.shapes.size.should_be 2
|
379
|
+
canvas.shapes[0].kind_of?(Circle).should_be true
|
380
|
+
canvas.shapes[1].should_be Square(20, 30, 40)
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'should accept a hash of types' do
|
384
|
+
class ::Canvas < Doodle
|
385
|
+
has :shapes, :collect => { :circle => Circle, :square => Square }
|
386
|
+
end
|
387
|
+
canvas = Canvas do
|
388
|
+
circle 10,10,5
|
389
|
+
square 20,30,40
|
390
|
+
end
|
391
|
+
canvas.shapes.size.should_be 2
|
392
|
+
canvas.shapes[0].kind_of?(Circle).should_be true
|
393
|
+
canvas.shapes[1].kind_of?(Square).should_be true
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|