doodle 0.0.4 → 0.0.5

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/ChangeLog CHANGED
@@ -1,12 +1,41 @@
1
- 2008-03-12 seano <sean.ohalpin@gmail.com>
1
+ = ChangeLog for doodle
2
2
 
3
- * added init_spec
3
+ == 0.0.5 / 2008-03-16
4
4
 
5
- * tweaked Rakefile (don't run profile by default, fix dcov task)
5
+ - added collector_spec
6
6
 
7
- * allow :collect => :name form for collecting raw
8
- objects (i.e. don't do a klass.new(*args) on values)
7
+ - tidied up validation specs
9
8
 
10
- * use snake_case (from facets) when generating name from class for
11
- collect
9
+ - fixed dumb bug where validations were getting duplicated resulting from using push on the source array. D'oh!
10
+
11
+ - added Doodle::DoodleInfo.raise_exception_on_error - set to false to have errors collected only
12
+ without raising an exception (which is the default behaviour)
13
+ - errors - returns array of error information
14
+ - clear_errors - clears errors array
15
+ - this is still a bit rough - work in progress
16
+
17
+ - fixed problem with init borking when given non-cloneable object (e.g. Fixnum)
18
+
19
+ - added serialize_spec
20
+
21
+ - validate! now validates all attributes too by default (no need to use validate!(true) any more)
22
+
23
+ == 2008-03-15 Sean O'Halpin <ohalps01@MC-S001853.local>
24
+
25
+ - added option (all=true|false) to validate! if true, will
26
+ validate all attributes as well as invoking object level validation
27
+
28
+ - made validate! public
29
+
30
+ == 2008-03-12 seano <sean.ohalpin@gmail.com>
31
+
32
+ - added init_spec
33
+
34
+ - tweaked Rakefile (don't run profile by default, fix dcov task)
35
+
36
+ - allow :collect => :name form for collecting raw
37
+ objects (i.e. don't do a klass.new(*args) on values)
38
+
39
+ - use snake_case (from facets) when generating name from class for
40
+ collect
12
41
 
@@ -1,11 +1,10 @@
1
1
  # doodle
2
2
  # Copyright (C) 2007 by Sean O'Halpin, 2007-11-24
3
3
 
4
- require 'molic_orderedhash' # todo[replace this with own (required function only) version]
4
+ require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
5
5
 
6
- # *doodle* is my attempt at a metaprogramming framework that does not
7
- # have to inject methods into core Ruby objects such as Object, Class
8
- # and Module.
6
+ # *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
7
+ # have pollute core Ruby objects such as Object, Class and Module.
9
8
 
10
9
  # While doodle itself is useful for defining classes, my main goal is to
11
10
  # come up with a useful DSL notation for class definitions which can be
@@ -16,16 +15,16 @@ require 'molic_orderedhash' # todo[replace this with own (required function onl
16
15
  module Doodle
17
16
  module Debug
18
17
  class << self
19
- # output result of block if DEBUG_DOODLE set
18
+ # output result of block if ENV['DEBUG_DOODLE'] set
20
19
  def d(&block)
21
20
  p(block.call) if ENV['DEBUG_DOODLE']
22
21
  end
23
22
  end
24
23
  end
25
24
 
26
- module Utils
27
- # Unnest arrays by one level of nesting - for example, [1, [[2], 3]] => [1, [2], 3].
28
- # This is a function to avoid changing base classes.
25
+ # Set of utility functions to avoid changing base classes
26
+ module Utils
27
+ # Unnest arrays by one level of nesting, e.g. [1, [[2], 3]] => [1, [2], 3].
29
28
  def self.flatten_first_level(enum)
30
29
  enum.inject([]) {|arr, i| if i.kind_of? Array then arr.push(*i) else arr.push(i) end }
31
30
  end
@@ -190,10 +189,6 @@ module Doodle
190
189
  break
191
190
  end
192
191
  end
193
- # d { [:collect_inherited, :result, message, result] }
194
- if self.class.respond_to?(message)
195
- result.unshift(*self.class.send(message))
196
- end
197
192
  result
198
193
  end
199
194
  private :collect_inherited
@@ -255,11 +250,20 @@ module Doodle
255
250
 
256
251
  class DoodleInfo
257
252
  DOODLES = {}
253
+ @@raise_exception_on_error = true
258
254
  attr_accessor :local_attributes
259
255
  attr_accessor :local_validations
260
256
  attr_accessor :local_conversions
261
257
  attr_accessor :validation_on
262
258
  attr_accessor :arg_order
259
+ attr_accessor :errors
260
+
261
+ def self.raise_exception_on_error
262
+ @@raise_exception_on_error
263
+ end
264
+ def self.raise_exception_on_error=(tf)
265
+ @@raise_exception_on_error = tf
266
+ end
263
267
 
264
268
  def initialize(object)
265
269
  @local_attributes = OrderedHash.new
@@ -267,8 +271,12 @@ module Doodle
267
271
  @validation_on = true
268
272
  @local_conversions = {}
269
273
  @arg_order = []
274
+ @errors = []
275
+
270
276
  oid = object.object_id
271
277
  ObjectSpace.define_finalizer(object) do
278
+ # this seems to be called only on exit
279
+ Doodle::Debug.d { "finalizing #{oid}" }
272
280
  DOODLES.delete(oid)
273
281
  end
274
282
  end
@@ -281,13 +289,36 @@ module Doodle
281
289
  include Inherited
282
290
 
283
291
  # this is the only way to get at internal values
284
- # FIXME: this is going to leak memory
292
+ # FIXME: this is going to leak memory (I think)
285
293
 
286
294
  def __doodle__
287
295
  DoodleInfo::DOODLES[object_id] ||= DoodleInfo.new(self)
288
296
  end
289
297
  private :__doodle__
290
298
 
299
+ # handle errors either by collecting in :errors or raising an exception
300
+ def handle_error(name, *args)
301
+ __doodle__.errors << [name, *args]
302
+ if DoodleInfo.raise_exception_on_error
303
+ raise *args
304
+ end
305
+ end
306
+
307
+ # get list of errors
308
+ def errors
309
+ errs = attributes.inject([]) do |e, (n, a)|
310
+ e.push(*a.send(:__doodle__).errors)
311
+ e
312
+ end
313
+ errs.push(__doodle__.errors).reject{|x| x.size == 0}
314
+ end
315
+ def clear_errors
316
+ attributes.each do |n, a|
317
+ a.send(:__doodle__).errors.clear
318
+ end
319
+ __doodle__.errors.clear
320
+ end
321
+
291
322
  # return attributes defined in instance
292
323
  def local_attributes
293
324
  __doodle__.local_attributes
@@ -321,7 +352,9 @@ module Doodle
321
352
  # - if tf == false, returns only those validations defined in the current object/class
322
353
  def validations(tf = true)
323
354
  if tf
324
- local_validations.push(*collect_inherited(:local_validations))
355
+ #p [:inherited_validations, collect_inherited(:local_validations)]
356
+ #p [:local_validations, local_validations]
357
+ local_validations + collect_inherited(:local_validations)
325
358
  else
326
359
  local_validations
327
360
  end
@@ -340,7 +373,7 @@ module Doodle
340
373
  if tf
341
374
  a = collect_inherited(:local_conversions).inject(OrderedHash.new){ |hash, item|
342
375
  #p [:hash, hash, :item, item]
343
- hash.merge(Hash[*item])
376
+ hash.merge(OrderedHash[*item])
344
377
  }.merge(self.local_conversions)
345
378
  # d { [:conversions, self.to_s, a] }
346
379
  a
@@ -353,7 +386,7 @@ module Doodle
353
386
  def lookup_attribute(name)
354
387
  # (look at singleton attributes first)
355
388
  # fixme[this smells like a hack to me - why not handled in attributes?]
356
- att = meta.attributes[name] || attributes[name]
389
+ meta.attributes[name] || attributes[name]
357
390
  end
358
391
  private :lookup_attribute
359
392
 
@@ -387,23 +420,19 @@ module Doodle
387
420
  v
388
421
  else
389
422
  # handle default
423
+ # Note: use :init => value to cover cases where defaults don't work
424
+ # (e.g. arrays that disappear when you go out of scope)
390
425
  att = lookup_attribute(name)
391
- #d { [:getter, name, att, block] }
426
+ #d { [:getter, name, att, block] }
392
427
  if att.default_defined?
393
428
  if att.default.kind_of?(Proc)
394
- default = instance_eval(&att.default)
429
+ instance_eval(&att.default)
395
430
  else
396
- default = att.default
431
+ att.default
397
432
  end
398
- #d { [:_getter, :default, name, default] } Note: once the
399
- # default is accessed, the instance variable is set. I think
400
- # I would prefer not to do this and to have :init => value
401
- # instead to cover cases where defaults don't work
402
- # (e.g. arrays that disappear when you go out of scope)
403
- #instance_variable_set("@#{name}", default)
404
- default
405
433
  else
406
- raise NoDefaultError, "'#{name}' has no default defined", [caller[-1]]
434
+ # This is an internal error (i.e. shouldn't happen)
435
+ handle_error name, NoDefaultError, "Internal error - '#{name}' has no default defined", [caller[-1]]
407
436
  end
408
437
  end
409
438
  end
@@ -424,7 +453,7 @@ module Doodle
424
453
  #d { [:_setter, :instance_variable_set, ivar, args ] }
425
454
  v = instance_variable_set(ivar, *args)
426
455
  end
427
- validate!
456
+ validate!(false)
428
457
  v
429
458
  end
430
459
  private :_setter
@@ -479,7 +508,7 @@ module Doodle
479
508
  end
480
509
  end
481
510
  rescue => e
482
- raise ValidationError, e.to_s, [caller[-1]]
511
+ handle_error name, ConversionError, e.to_s, [caller[-1]]
483
512
  end
484
513
  value
485
514
  end
@@ -491,7 +520,7 @@ module Doodle
491
520
  validations.each do |v|
492
521
  Doodle::Debug.d { [:validate, self, v, args ] }
493
522
  if !v.block[value]
494
- raise ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
523
+ handle_error name, ValidationError, "#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
495
524
  end
496
525
  end
497
526
  #d { [:validate, :value, value ] }
@@ -547,7 +576,7 @@ module Doodle
547
576
  name = args.shift.to_sym
548
577
  # d { [:has2, name, args] }
549
578
  key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
550
- raise ArgumentError, "Too many arguments" if positional_args.size > 0
579
+ handle_error name, ArgumentError, "Too many arguments" if positional_args.size > 0
551
580
  # d { [:has_args, self, key_values, positional_args, args] }
552
581
  params = { :name => name }
553
582
  params = key_values.inject(params){ |acc, item| acc.merge(item)}
@@ -581,13 +610,13 @@ module Doodle
581
610
  #p [:arg_order, 1, self, self.class, args]
582
611
  args.uniq!
583
612
  args.each do |x|
584
- raise Exception, "#{x} not a Symbol" if !(x.class <= Symbol)
585
- raise Exception, "#{x} not an attribute name" if !attributes.keys.include?(x)
613
+ handle_error :arg_order, ArgumentError, "#{x} not a Symbol" if !(x.class <= Symbol)
614
+ handle_error :arg_order, NameError, "#{x} not an attribute name" if !attributes.keys.include?(x)
586
615
  end
587
616
  __doodle__.arg_order = args
588
617
  rescue Exception => e
589
618
  #p [InvalidOrderError, e.to_s]
590
- raise InvalidOrderError, e.to_s, [caller[-1]]
619
+ handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
591
620
  end
592
621
  else
593
622
  #p [:arg_order, 3, self, self.class, :default]
@@ -610,7 +639,7 @@ module Doodle
610
639
  # d { [:initialize, :arg_keywords, arg_keywords] }
611
640
 
612
641
  # set up initial values with ~clones~ of specified values (so not shared between instances)
613
- init_values = attributes.select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)| hash[n] = a.init.clone; hash }
642
+ init_values = attributes.select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)| hash[n] = a.init.clone rescue a.init; hash }
614
643
 
615
644
  # add to start of key_values (so can be overridden by params)
616
645
  key_values.unshift(init_values)
@@ -638,8 +667,8 @@ module Doodle
638
667
  private :ivar_defined?
639
668
 
640
669
  # validate this object by applying all validations in sequence
641
- # - if all == true, validate all attributes, e.g. when loaded from YAML
642
- def validate!(all = false)
670
+ # - if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only
671
+ def validate!(all = true)
643
672
  #Doodle::Debug.d { [:validate!, self] }
644
673
  #Doodle::Debug.d { [:validate!, self, __doodle__.validation_on] }
645
674
  if __doodle__.validation_on
@@ -648,7 +677,7 @@ module Doodle
648
677
  if att.name == :default || att.default_defined?
649
678
  # nop
650
679
  elsif !ivar_defined?(att.name)
651
- raise ArgumentError, "#{self} missing required attribute '#{name}'", [caller[-1]]
680
+ handle_error name, ArgumentError, "#{self} missing required attribute '#{name}'", [caller[-1]]
652
681
  end
653
682
  # if all == true, validate all attributes - e.g. when loaded from YAML
654
683
  if all
@@ -657,9 +686,9 @@ module Doodle
657
686
  end
658
687
 
659
688
  validations.each do |v|
660
- Doodle::Debug.d { [:validate!, self, v ] }
689
+ #Doodle::Debug.d { [:validate!, self, v ] }
661
690
  if !instance_eval(&v.block)
662
- raise ValidationError, "#{ self.inspect } must #{ v.message }", [caller[-1]]
691
+ handle_error :validate!, ValidationError, "#{ self.inspect } must #{ v.message }", [caller[-1]]
663
692
  end
664
693
  end
665
694
  end
@@ -676,7 +705,7 @@ module Doodle
676
705
  ensure
677
706
  __doodle__.validation_on = old_validation
678
707
  end
679
- validate!
708
+ validate!(false)
680
709
  v
681
710
  end
682
711
 
@@ -775,7 +804,7 @@ module Doodle
775
804
  # it will fail because Attribute is not a kind of Date -
776
805
  # obviously, I have to think about this some more :S
777
806
  #
778
- def validate!
807
+ def validate!(all = true)
779
808
  end
780
809
 
781
810
  # is this attribute optional? true if it has a default defined for it
@@ -1,125 +1,108 @@
1
- require 'lib/spec_helper'
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
2
 
3
3
  describe 'arg_order' do
4
- before :each do
5
- raise_if_defined :Foo
6
- class Foo < Doodle::Base
7
- has :name
8
- has :extra
9
- has :value
10
- arg_order :value, :name
4
+ temporary_constant :Foo do
5
+ before :each do
6
+ class Foo < Doodle::Base
7
+ has :name
8
+ has :extra
9
+ has :value
10
+ arg_order :value, :name
11
+ end
12
+ end
13
+ it 'should specify order of positional arguments' do
14
+ foo = Foo.new 1, 2, 3
15
+ foo.value.should == 1
16
+ foo.name.should == 2
17
+ foo.extra.should == 3
11
18
  end
12
- end
13
- after :each do
14
- undefine_const :Foo
15
- end
16
- it 'should specify order of positional arguments' do
17
- foo = Foo.new 1, 2, 3
18
- foo.value.should == 1
19
- foo.name.should == 2
20
- foo.extra.should == 3
21
- end
22
19
 
23
- it 'should allow only symbols as arguments to arg_order' do
24
- proc { Foo.arg_order Foo}.should raise_error(Doodle::InvalidOrderError)
25
- proc { Foo.arg_order 1}.should raise_error(Doodle::InvalidOrderError)
26
- proc { Foo.arg_order Date.new}.should raise_error(Doodle::InvalidOrderError)
27
- end
20
+ it 'should allow only symbols as arguments to arg_order' do
21
+ proc { Foo.arg_order Foo}.should raise_error(Doodle::InvalidOrderError)
22
+ proc { Foo.arg_order 1}.should raise_error(Doodle::InvalidOrderError)
23
+ proc { Foo.arg_order Date.new}.should raise_error(Doodle::InvalidOrderError)
24
+ end
28
25
 
29
- it 'should not allow invalid positional arguments' do
30
- proc { Foo.arg_order :smoo}.should raise_error(Doodle::InvalidOrderError)
31
- proc { Foo.arg_order :name, :value}.should_not raise_error
32
- end
26
+ it 'should not allow invalid positional arguments' do
27
+ proc { Foo.arg_order :smoo}.should raise_error(Doodle::InvalidOrderError)
28
+ proc { Foo.arg_order :name, :value}.should_not raise_error
29
+ end
30
+ end
33
31
  end
34
32
 
35
33
  describe 'arg_order' do
36
- before :each do
37
- raise_if_defined :Foo, :Bar
38
- class Foo < Doodle::Base
39
- has :name
34
+ temporary_constants :Foo, :Bar do
35
+ before :each do
36
+ class Foo < Doodle::Base
37
+ has :name
38
+ end
39
+ class Bar < Foo
40
+ has :value
41
+ end
40
42
  end
41
- class Bar < Foo
42
- has :value
43
+ it 'should specify order of positional arguments' do
44
+ f = Bar.new 1, 2
45
+ f.name.should == 1
46
+ f.value.should == 2
43
47
  end
44
48
  end
45
- after :each do
46
- undefine_const(:Bar)
47
- undefine_const(:Foo)
48
- end
49
- it 'should specify order of positional arguments' do
50
- f = Bar.new 1, 2
51
- f.name.should == 1
52
- f.value.should == 2
53
- end
54
49
  end
55
50
 
56
51
  describe 'arg_order' do
57
- before :each do
58
- raise_if_defined(:Foo, :Bar)
59
- class Foo < Doodle::Base
60
- has :name
52
+ temporary_constants :Foo, :Bar do
53
+ before :each do
54
+ class Foo < Doodle::Base
55
+ has :name
56
+ end
57
+ class Bar < Foo
58
+ has :value
59
+ arg_order :value, :name
60
+ end
61
61
  end
62
- class Bar < Foo
63
- has :value
64
- arg_order :value, :name
65
- end
66
- end
67
- after :each do
68
- undefine_const(:Bar)
69
- undefine_const(:Foo)
62
+ it 'should specify order of positional arguments' do
63
+ f = Bar.new 1, 2
64
+ f.value.should == 1
65
+ f.name.should == 2
66
+ end
70
67
  end
71
- it 'should specify order of positional arguments' do
72
- f = Bar.new 1, 2
73
- f.value.should == 1
74
- f.name.should == 2
75
- end
76
-
77
68
  end
78
69
 
79
70
  describe 'arg_order' do
80
- before :each do
81
- raise_if_defined(:Foo, :Bar)
82
- class Foo < Doodle::Base
83
- has :name, :default => proc { self.class.to_s.downcase }
71
+ temporary_constants :Foo, :Bar do
72
+ before :each do
73
+ class Foo < Doodle::Base
74
+ has :name, :default => proc { self.class.to_s.downcase }
75
+ end
76
+ class Bar < Foo
77
+ has :value
78
+ arg_order :value, :name
79
+ end
84
80
  end
85
- class Bar < Foo
86
- has :value
87
- arg_order :value, :name
88
- end
89
- end
90
- after :each do
91
- undefine_const(:Bar, :Foo)
92
- end
93
81
 
94
- it 'should specify order of positional arguments' do
95
- f = Bar.new 1
96
- f.value.should == 1
97
- f.name.should == "bar"
98
- end
99
-
82
+ it 'should specify order of positional arguments' do
83
+ f = Bar.new 1
84
+ f.value.should == 1
85
+ f.name.should == "bar"
86
+ end
87
+ end
100
88
  end
101
89
 
102
90
  describe 'arg_order' do
103
- after :each do
104
- undefine_const(:Bar, :Foo)
105
- end
106
- before :each do
107
- raise_if_defined(:Foo, :Bar)
108
- class Foo < Doodle::Base
109
- has :name
91
+ temporary_constants :Foo, :Bar do
92
+ before :each do
93
+ class Foo < Doodle::Base
94
+ has :name
95
+ end
96
+ class Bar < Foo
97
+ has :value
98
+ arg_order :value, :name
99
+ end
110
100
  end
111
- class Bar < Foo
112
- has :value
113
- arg_order :value, :name
114
- end
115
- end
116
101
 
117
- it 'should specify order of positional arguments' do
118
- f = Bar.new 1, "bar"
119
- f.value.should == 1
120
- f.name.should == "bar"
121
- end
122
-
102
+ it 'should specify order of positional arguments' do
103
+ f = Bar.new 1, "bar"
104
+ f.value.should == 1
105
+ f.name.should == "bar"
106
+ end
107
+ end
123
108
  end
124
-
125
- raise_if_defined(:Foo, :Bar)