doodle 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/History.txt +24 -0
  2. data/Manifest.txt +26 -1
  3. data/README.txt +9 -8
  4. data/lib/doodle.rb +43 -1496
  5. data/lib/doodle/app.rb +6 -0
  6. data/lib/doodle/attribute.rb +165 -0
  7. data/lib/doodle/base.rb +180 -0
  8. data/lib/doodle/collector-1.9.rb +72 -0
  9. data/lib/doodle/collector.rb +191 -0
  10. data/lib/doodle/comparable.rb +8 -0
  11. data/lib/doodle/conversion.rb +80 -0
  12. data/lib/doodle/core.rb +42 -0
  13. data/lib/doodle/datatype-holder.rb +39 -0
  14. data/lib/doodle/debug.rb +20 -0
  15. data/lib/doodle/deferred.rb +13 -0
  16. data/lib/doodle/equality.rb +21 -0
  17. data/lib/doodle/exceptions.rb +29 -0
  18. data/lib/doodle/factory.rb +91 -0
  19. data/lib/doodle/getter-setter.rb +154 -0
  20. data/lib/doodle/info.rb +298 -0
  21. data/lib/doodle/inherit.rb +40 -0
  22. data/lib/doodle/json.rb +38 -0
  23. data/lib/doodle/marshal.rb +16 -0
  24. data/lib/doodle/normalized_array.rb +512 -0
  25. data/lib/doodle/normalized_hash.rb +356 -0
  26. data/lib/doodle/ordered-hash.rb +8 -0
  27. data/lib/doodle/singleton.rb +23 -0
  28. data/lib/doodle/smoke-and-mirrors.rb +23 -0
  29. data/lib/doodle/to_hash.rb +17 -0
  30. data/lib/doodle/utils.rb +173 -11
  31. data/lib/doodle/validation.rb +122 -0
  32. data/lib/doodle/version.rb +1 -1
  33. data/lib/molic_orderedhash.rb +24 -10
  34. data/spec/assigned_spec.rb +45 -0
  35. data/spec/attributes_spec.rb +7 -7
  36. data/spec/collector_spec.rb +100 -13
  37. data/spec/doodle_context_spec.rb +5 -5
  38. data/spec/from_spec.rb +43 -3
  39. data/spec/json_spec.rb +232 -0
  40. data/spec/member_init_spec.rb +11 -11
  41. data/spec/modules_spec.rb +4 -4
  42. data/spec/multi_collector_spec.rb +91 -0
  43. data/spec/must_spec.rb +32 -0
  44. data/spec/spec_helper.rb +14 -4
  45. data/spec/specialized_attribute_class_spec.rb +2 -2
  46. data/spec/typed_collector_spec.rb +57 -0
  47. data/spec/xml_spec.rb +8 -8
  48. 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
@@ -2,7 +2,7 @@ class Doodle #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 2
5
- TINY = 2
5
+ TINY = 3
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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
@@ -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.collect_inherited(:local_attributes).map { |x| x[0]} }.should == []
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
@@ -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
- proc {
116
+ no_error {
117
117
  event = Event do
118
118
  place "Stage 1"
119
119
  place "Stage 2"
120
120
  end
121
- }.should_not raise_error
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
- proc {
153
+ no_error {
154
154
  event = Event(data)
155
- }.should_not raise_error
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( { 5 => "World" } )
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
- proc {
259
+ no_error {
259
260
  text = Text do
260
261
  line "line 1"
261
262
  line "line 2"
262
263
  end
263
- }.should_not raise_error
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
- proc {
295
+ no_error {
295
296
  signed_by = SignedBy do
296
297
  signature "Sean"
297
298
  end
298
- }.should_not raise_error
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
+