doodle 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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)