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 +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)
|