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 +36 -7
- data/lib/doodle.rb +71 -42
- data/spec/arg_order_spec.rb +81 -98
- data/spec/attributes_spec.rb +70 -82
- data/spec/bugs_spec.rb +21 -0
- data/spec/class_spec.rb +63 -70
- data/spec/collector_spec.rb +97 -0
- data/spec/conversion_spec.rb +1 -3
- data/spec/defaults_spec.rb +104 -125
- data/spec/doodle_spec.rb +240 -227
- data/spec/flatten_first_level_spec.rb +4 -3
- data/spec/init_spec.rb +45 -49
- data/spec/required_spec.rb +9 -17
- data/spec/serialization_spec.rb +32 -0
- data/spec/superclass_spec.rb +17 -20
- data/spec/validation_spec.rb +79 -88
- metadata +5 -6
- data/examples/example-01.rdoc +0 -16
- data/examples/example-02.rdoc +0 -62
- data/examples/foo.rb +0 -10
- data/lib/spec_helper.rb +0 -19
data/ChangeLog
CHANGED
@@ -1,12 +1,41 @@
|
|
1
|
-
|
1
|
+
= ChangeLog for doodle
|
2
2
|
|
3
|
-
|
3
|
+
== 0.0.5 / 2008-03-16
|
4
4
|
|
5
|
-
|
5
|
+
- added collector_spec
|
6
6
|
|
7
|
-
|
8
|
-
objects (i.e. don't do a klass.new(*args) on values)
|
7
|
+
- tidied up validation specs
|
9
8
|
|
10
|
-
|
11
|
-
|
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
|
|
data/lib/doodle.rb
CHANGED
@@ -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
|
4
|
+
require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
|
5
5
|
|
6
|
-
# *doodle* is my attempt at
|
7
|
-
# have
|
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
|
-
|
27
|
-
|
28
|
-
#
|
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
|
-
|
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(
|
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
|
-
|
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,
|
426
|
+
#d { [:getter, name, att, block] }
|
392
427
|
if att.default_defined?
|
393
428
|
if att.default.kind_of?(Proc)
|
394
|
-
|
429
|
+
instance_eval(&att.default)
|
395
430
|
else
|
396
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
585
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
data/spec/arg_order_spec.rb
CHANGED
@@ -1,125 +1,108 @@
|
|
1
|
-
require '
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
|
3
3
|
describe 'arg_order' do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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)
|