doodle 0.2.2 → 0.2.3
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 +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
|
+
|