doodle 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +38 -0
- data/lib/doodle.rb +192 -212
- data/lib/doodle/version.rb +1 -1
- data/spec/attributes_spec.rb +25 -24
- data/spec/bugs_spec.rb +2 -2
- data/spec/class_spec.rb +5 -5
- data/spec/conversion_spec.rb +1 -1
- data/spec/defaults_spec.rb +8 -8
- data/spec/doodle_context_spec.rb +2 -2
- data/spec/doodle_spec.rb +22 -22
- data/spec/from_spec.rb +43 -0
- data/spec/has_spec.rb +15 -6
- data/spec/inheritance_spec.rb +11 -12
- data/spec/init_spec.rb +6 -6
- data/spec/kind_spec.rb +17 -0
- data/spec/singleton_spec.rb +12 -12
- data/spec/specialized_attribute_class_spec.rb +4 -4
- data/spec/superclass_spec.rb +6 -6
- data/spec/validation2_spec.rb +4 -4
- data/spec/validation_spec.rb +5 -5
- metadata +4 -2
data/History.txt
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
== 0.1.8 / 2008-05-13
|
2
|
+
- Features:
|
3
|
+
- now applies instance level conversions (class level #from) to
|
4
|
+
attribute values, e.g.
|
5
|
+
|
6
|
+
class Name < String
|
7
|
+
include Doodle::Core
|
8
|
+
from String do |s|
|
9
|
+
Name.new(s)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Person < Doodle
|
14
|
+
has Name
|
15
|
+
end
|
16
|
+
|
17
|
+
person = Person 'Arthur'
|
18
|
+
person.name.class # => Name
|
19
|
+
|
20
|
+
- better error reporting
|
21
|
+
- kind can now take an array of classes or modules to match against,
|
22
|
+
- e.g.
|
23
|
+
|
24
|
+
has :name, :kind => [String, Symbol]
|
25
|
+
|
26
|
+
- kind with no args now returns an array of kinds (possibly empty)
|
27
|
+
|
28
|
+
- Bug fixes:
|
29
|
+
- #has with class param was not enforcing :kind constraint
|
30
|
+
- moved more methods into DoodleInfo. You should now access metadata
|
31
|
+
via:
|
32
|
+
- obj.doodle.attributes
|
33
|
+
- obj.doodle.conversions
|
34
|
+
- obj.doodle.validations
|
35
|
+
- obj.doodle.parent
|
36
|
+
- obj.doodle.class_attributes
|
37
|
+
- collectors initializing too late when defined at class level
|
38
|
+
|
1
39
|
== 0.1.7 / 2008-05-10
|
2
40
|
- Features
|
3
41
|
- #has now accepts class constant in name position and generates name
|
data/lib/doodle.rb
CHANGED
@@ -62,6 +62,18 @@ class Doodle
|
|
62
62
|
def const_resolve(constant)
|
63
63
|
constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
|
64
64
|
end
|
65
|
+
# convert keys to symbols - updates target hash
|
66
|
+
def symbolize_keys!(hash)
|
67
|
+
hash.keys.each do |key|
|
68
|
+
sym_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
69
|
+
hash[sym_key] = hash.delete(key)
|
70
|
+
end
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
# convert keys to symbols
|
74
|
+
def symbolize_keys(hash)
|
75
|
+
symbolize_keys(hash.dup)
|
76
|
+
end
|
65
77
|
end
|
66
78
|
end
|
67
79
|
|
@@ -91,12 +103,12 @@ class Doodle
|
|
91
103
|
end
|
92
104
|
|
93
105
|
# provides more direct access to the singleton class and a way to
|
94
|
-
# treat Modules and Classes equally in a meta context
|
106
|
+
# treat singletons, Modules and Classes equally in a meta context
|
95
107
|
module SelfClass
|
96
108
|
# return the 'singleton class' of an object, optionally executing
|
97
109
|
# a block argument in the (module/class) context of that object
|
98
110
|
def singleton_class(&block)
|
99
|
-
sc =
|
111
|
+
sc = class << self; self; end
|
100
112
|
sc.module_eval(&block) if block_given?
|
101
113
|
sc
|
102
114
|
end
|
@@ -224,8 +236,8 @@ class Doodle
|
|
224
236
|
def collect_inherited(message)
|
225
237
|
result = []
|
226
238
|
parents.each do |klass|
|
227
|
-
if klass.respond_to?(message)
|
228
|
-
result.unshift(*klass.__send__(message))
|
239
|
+
if klass.respond_to?(:doodle) && klass.doodle.respond_to?(message)
|
240
|
+
result.unshift(*klass.doodle.__send__(message))
|
229
241
|
else
|
230
242
|
break
|
231
243
|
end
|
@@ -237,33 +249,34 @@ class Doodle
|
|
237
249
|
if tf
|
238
250
|
collect_inherited(method).inject(OrderedHash.new){ |hash, item|
|
239
251
|
hash.merge(OrderedHash[*item])
|
240
|
-
}.merge(@this.__send__(method))
|
252
|
+
}.merge(@this.doodle.__send__(method))
|
241
253
|
else
|
242
|
-
@this.__send__(method)
|
254
|
+
@this.doodle.__send__(method)
|
243
255
|
end
|
244
256
|
end
|
245
|
-
|
257
|
+
|
246
258
|
# returns array of Attributes
|
247
259
|
# - if tf == true, returns all inherited attributes
|
248
260
|
# - if tf == false, returns only those attributes defined in the current object/class
|
249
261
|
def attributes(tf = true)
|
250
|
-
results = handle_inherited_hash(tf, :
|
262
|
+
results = handle_inherited_hash(tf, :local_attributes)
|
251
263
|
# if an instance, include the singleton_class attributes
|
252
264
|
if !@this.kind_of?(Class) && @this.singleton_class.doodle.respond_to?(:attributes)
|
253
|
-
results = results.merge(@this.singleton_class.
|
265
|
+
results = results.merge(@this.singleton_class.doodle.attributes)
|
254
266
|
end
|
255
267
|
results
|
256
268
|
end
|
257
269
|
|
270
|
+
# return class level attributes
|
258
271
|
def class_attributes
|
259
272
|
attrs = OrderedHash.new
|
260
273
|
if @this.kind_of?(Class)
|
261
274
|
attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
|
262
275
|
hash.merge(OrderedHash[*item])
|
263
|
-
}.merge(@this.singleton_class.respond_to?(:
|
276
|
+
}.merge(@this.singleton_class.doodle.respond_to?(:attributes) ? @this.singleton_class.doodle.attributes : { })
|
264
277
|
attrs
|
265
278
|
else
|
266
|
-
@this.class.class_attributes
|
279
|
+
@this.class.doodle.class_attributes
|
267
280
|
end
|
268
281
|
end
|
269
282
|
|
@@ -275,7 +288,7 @@ class Doodle
|
|
275
288
|
# by name and kind respectively, so only the most recent
|
276
289
|
# applies
|
277
290
|
|
278
|
-
local_validations + collect_inherited(:
|
291
|
+
local_validations + collect_inherited(:local_validations)
|
279
292
|
else
|
280
293
|
local_validations
|
281
294
|
end
|
@@ -295,7 +308,108 @@ class Doodle
|
|
295
308
|
# - if tf == true, returns all inherited conversions
|
296
309
|
# - if tf == false, returns only those conversions defined in the current object/class
|
297
310
|
def conversions(tf = true)
|
298
|
-
handle_inherited_hash(tf, :
|
311
|
+
handle_inherited_hash(tf, :local_conversions)
|
312
|
+
end
|
313
|
+
|
314
|
+
# fixme: move
|
315
|
+
def initial_values(tf = true)
|
316
|
+
attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
|
317
|
+
#p [:initial_values, a.name]
|
318
|
+
hash[n] = case a.init
|
319
|
+
when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
|
320
|
+
# uncloneable values
|
321
|
+
#p [:initial_values, :special, a.name, a.init]
|
322
|
+
a.init
|
323
|
+
when DeferredBlock
|
324
|
+
#p [:initial_values, self, DeferredBlock, a.name]
|
325
|
+
begin
|
326
|
+
@this.instance_eval(&a.init.block)
|
327
|
+
rescue Object => e
|
328
|
+
#p [:exception_in_deferred_block, e]
|
329
|
+
raise
|
330
|
+
end
|
331
|
+
else
|
332
|
+
#p [:initial_values, :clone, a.name]
|
333
|
+
begin
|
334
|
+
a.init.clone
|
335
|
+
rescue Exception => e
|
336
|
+
warn "tried to clone #{a.init.class} in :init option"
|
337
|
+
#p [:initial_values, :exception, a.name, e]
|
338
|
+
a.init
|
339
|
+
end
|
340
|
+
end
|
341
|
+
hash
|
342
|
+
}
|
343
|
+
end
|
344
|
+
|
345
|
+
# turn off validation, execute block, then set validation to same
|
346
|
+
# state as it was before +defer_validation+ was called - can be nested
|
347
|
+
# fixme: move
|
348
|
+
def defer_validation(&block)
|
349
|
+
old_validation = self.validation_on
|
350
|
+
self.validation_on = false
|
351
|
+
v = nil
|
352
|
+
begin
|
353
|
+
v = @this.instance_eval(&block)
|
354
|
+
ensure
|
355
|
+
self.validation_on = old_validation
|
356
|
+
end
|
357
|
+
@this.validate!(false)
|
358
|
+
v
|
359
|
+
end
|
360
|
+
|
361
|
+
# helper function to initialize from hash - this is safe to use
|
362
|
+
# after initialization (validate! is called if this method is
|
363
|
+
# called after initialization)
|
364
|
+
def initialize_from_hash(*args)
|
365
|
+
#!p [:doodle_initialize_from_hash, :args, *args]
|
366
|
+
defer_validation do
|
367
|
+
# hash initializer
|
368
|
+
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
369
|
+
key_values, args = args.partition{ |x| x.kind_of?(Hash)}
|
370
|
+
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args] }
|
371
|
+
#!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
|
372
|
+
|
373
|
+
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
374
|
+
#init_values = initial_values
|
375
|
+
#!p [:init_values, init_values]
|
376
|
+
|
377
|
+
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
378
|
+
#arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
|
379
|
+
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
380
|
+
#!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]
|
381
|
+
|
382
|
+
# merge all hash args into one
|
383
|
+
key_values = key_values.inject(arg_keywords) { |hash, item|
|
384
|
+
#!p [self.class, :doodle_initialize_from_hash, :merge, hash, item]
|
385
|
+
hash.merge(item)
|
386
|
+
}
|
387
|
+
#!p [self.class, :doodle_initialize_from_hash, :key_values2, key_values]
|
388
|
+
|
389
|
+
# convert keys to symbols (note not recursively - only first level == doodle keywords)
|
390
|
+
Doodle::Utils.symbolize_keys!(key_values)
|
391
|
+
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values2, key_values, :args2, args] }
|
392
|
+
#!p [self.class, :doodle_initialize_from_hash, :key_values3, key_values]
|
393
|
+
|
394
|
+
# create attributes
|
395
|
+
key_values.keys.each do |key|
|
396
|
+
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
|
397
|
+
#p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
|
398
|
+
if respond_to?(key)
|
399
|
+
__send__(key, key_values[key])
|
400
|
+
else
|
401
|
+
# raise error if not defined
|
402
|
+
__doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}", (caller)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
# do init_values after user supplied values so init blocks can depend on user supplied values
|
406
|
+
#p [:getting_init_values, instance_variables]
|
407
|
+
__doodle__.initial_values.each do |key, value|
|
408
|
+
if !key_values.key?(key) && respond_to?(key)
|
409
|
+
__send__(key, value)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
299
413
|
end
|
300
414
|
|
301
415
|
end
|
@@ -382,62 +496,6 @@ class Doodle
|
|
382
496
|
end
|
383
497
|
end
|
384
498
|
|
385
|
-
# deprecated methods
|
386
|
-
def doodle_collect_inherited(message)
|
387
|
-
__doodle__.collect_inherited(message)
|
388
|
-
end
|
389
|
-
def doodle_parents
|
390
|
-
__doodle__.parents
|
391
|
-
end
|
392
|
-
|
393
|
-
# return attributes defined in instance
|
394
|
-
def doodle_local_attributes
|
395
|
-
__doodle__.local_attributes
|
396
|
-
end
|
397
|
-
protected :doodle_local_attributes
|
398
|
-
|
399
|
-
# returns array of Attributes
|
400
|
-
# - if tf == true, returns all inherited attributes
|
401
|
-
# - if tf == false, returns only those attributes defined in the current object/class
|
402
|
-
def doodle_attributes(tf = true)
|
403
|
-
__doodle__.attributes(tf)
|
404
|
-
end
|
405
|
-
|
406
|
-
# return attributes for class
|
407
|
-
def class_attributes
|
408
|
-
__doodle__.class_attributes
|
409
|
-
end
|
410
|
-
|
411
|
-
# the set of conversions defined in the current class (i.e. without inheritance)
|
412
|
-
# deprecated
|
413
|
-
def doodle_local_conversions
|
414
|
-
__doodle__.local_conversions
|
415
|
-
end
|
416
|
-
protected :doodle_local_conversions
|
417
|
-
|
418
|
-
# returns hash of conversions
|
419
|
-
# - if tf == true, returns all inherited conversions
|
420
|
-
# - if tf == false, returns only those conversions defined in the current object/class
|
421
|
-
# deprecated
|
422
|
-
def doodle_conversions(tf = true)
|
423
|
-
__doodle__.conversions(tf)
|
424
|
-
end
|
425
|
-
|
426
|
-
# the set of validations defined in the current class (i.e. without inheritance)
|
427
|
-
# deprecated
|
428
|
-
def doodle_local_validations
|
429
|
-
__doodle__.local_validations
|
430
|
-
end
|
431
|
-
protected :doodle_local_validations
|
432
|
-
|
433
|
-
# returns array of Validations
|
434
|
-
# - if tf == true, returns all inherited validations
|
435
|
-
# - if tf == false, returns only those validations defined in the current object/class
|
436
|
-
# deprecated
|
437
|
-
def doodle_validations(tf = true)
|
438
|
-
__doodle__.validations(tf)
|
439
|
-
end
|
440
|
-
|
441
499
|
# either get an attribute value (if no args given) or set it
|
442
500
|
# (using args and/or block)
|
443
501
|
# fixme: move
|
@@ -485,7 +543,7 @@ class Doodle
|
|
485
543
|
v
|
486
544
|
else
|
487
545
|
# This is an internal error (i.e. shouldn't happen)
|
488
|
-
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined",
|
546
|
+
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", (caller)
|
489
547
|
end
|
490
548
|
end
|
491
549
|
end
|
@@ -519,6 +577,7 @@ class Doodle
|
|
519
577
|
# if block passed, define a conversion from class
|
520
578
|
# if no args, apply conversion to arguments
|
521
579
|
def from(*args, &block)
|
580
|
+
#p [:from, self, args]
|
522
581
|
if block_given?
|
523
582
|
# set the rule for each arg given
|
524
583
|
args.each do |arg|
|
@@ -541,10 +600,16 @@ class Doodle
|
|
541
600
|
|
542
601
|
# add a validation that attribute must be of class <= kind
|
543
602
|
def kind(*args, &block)
|
603
|
+
@kind ||= []
|
544
604
|
if args.size > 0
|
605
|
+
@kind = [args].flatten
|
545
606
|
# todo[figure out how to handle kind being specified twice?]
|
546
|
-
@kind
|
547
|
-
|
607
|
+
if @kind.size > 2
|
608
|
+
kind_text = "a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # =>
|
609
|
+
else
|
610
|
+
kind_text = "a kind of #{@kind.to_s}"
|
611
|
+
end
|
612
|
+
__doodle__.local_validations << (Validation.new(kind_text) { |x| @kind.any? { |klass| x.kind_of?(klass) } })
|
548
613
|
else
|
549
614
|
@kind
|
550
615
|
end
|
@@ -553,7 +618,7 @@ class Doodle
|
|
553
618
|
# convert a value according to conversion rules
|
554
619
|
# fixme: move
|
555
620
|
def convert(owner, *args)
|
556
|
-
#!p [:convert, 1, owner, args]
|
621
|
+
#!p [:convert, 1, owner, args, __doodle__.conversions]
|
557
622
|
begin
|
558
623
|
args = args.map do |value|
|
559
624
|
#!p [:convert, 2, value]
|
@@ -564,27 +629,46 @@ class Doodle
|
|
564
629
|
else
|
565
630
|
#!p [:convert, 5, value]
|
566
631
|
# try to find nearest ancestor
|
567
|
-
|
568
|
-
#!p [:convert, 6,
|
569
|
-
matches =
|
632
|
+
this_ancestors = value.class.ancestors
|
633
|
+
#!p [:convert, 6, this_ancestors]
|
634
|
+
matches = this_ancestors & __doodle__.conversions.keys
|
570
635
|
#!p [:convert, 7, matches]
|
571
|
-
indexed_matches = matches.map{ |x|
|
636
|
+
indexed_matches = matches.map{ |x| this_ancestors.index(x)}
|
572
637
|
#!p [:convert, 8, indexed_matches]
|
573
638
|
if indexed_matches.size > 0
|
574
639
|
#!p [:convert, 9]
|
575
|
-
converter_class =
|
640
|
+
converter_class = this_ancestors[indexed_matches.min]
|
576
641
|
#!p [:convert, 10, converter_class]
|
577
642
|
if converter = __doodle__.conversions[converter_class]
|
578
643
|
#!p [:convert, 11, converter]
|
579
644
|
value = converter[*args]
|
580
645
|
#!p [:convert, 12, value]
|
581
646
|
end
|
647
|
+
else
|
648
|
+
#!p [:convert, 13, :kind, kind, name, value]
|
649
|
+
mappable_kinds = kind.select{ |x| x <= Doodle::Core }
|
650
|
+
#!p [:convert, 13.1, :kind, kind, mappable_kinds]
|
651
|
+
if mappable_kinds.size > 0
|
652
|
+
mappable_kinds.each do |mappable_kind|
|
653
|
+
#!p [:convert, 14, :kind_is_a_doodle, value.class, mappable_kind, mappable_kind.doodle.conversions, args]
|
654
|
+
if converter = mappable_kind.doodle.conversions[value.class]
|
655
|
+
#!p [:convert, 15, value, mappable_kind, args]
|
656
|
+
value = converter[*args]
|
657
|
+
break
|
658
|
+
else
|
659
|
+
#!p [:convert, 16, :no_conversion_for, value.class]
|
660
|
+
end
|
661
|
+
end
|
662
|
+
else
|
663
|
+
#!p [:convert, 17, :kind_has_no_conversions]
|
664
|
+
end
|
582
665
|
end
|
583
666
|
end
|
667
|
+
#!p [:convert, 18, value]
|
584
668
|
value
|
585
669
|
end
|
586
670
|
rescue Exception => e
|
587
|
-
owner.__doodle__.handle_error name, ConversionError, "#{
|
671
|
+
owner.__doodle__.handle_error name, ConversionError, "#{e.message}", (caller)
|
588
672
|
end
|
589
673
|
if args.size > 1
|
590
674
|
args
|
@@ -597,15 +681,20 @@ class Doodle
|
|
597
681
|
# fixme: move
|
598
682
|
def validate(owner, *args)
|
599
683
|
##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
600
|
-
#!p [:validate,
|
601
|
-
|
602
|
-
|
684
|
+
#!p [:validate, 1, args]
|
685
|
+
begin
|
686
|
+
value = convert(owner, *args)
|
687
|
+
rescue Exception => e
|
688
|
+
owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } - #{e.message}", (caller)
|
689
|
+
end
|
690
|
+
#!p [:validate, 2, args, :becomes, value]
|
603
691
|
__doodle__.validations.each do |v|
|
604
692
|
##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
|
605
693
|
if !v.block[value]
|
606
|
-
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })",
|
694
|
+
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", (caller)
|
607
695
|
end
|
608
696
|
end
|
697
|
+
#!p [:validate, 3, value]
|
609
698
|
value
|
610
699
|
end
|
611
700
|
|
@@ -670,50 +759,18 @@ class Doodle
|
|
670
759
|
begin
|
671
760
|
args = args.uniq
|
672
761
|
args.each do |x|
|
673
|
-
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol",
|
674
|
-
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name",
|
762
|
+
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", (caller) if !(x.class <= Symbol)
|
763
|
+
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", (caller) if !doodle.attributes.keys.include?(x)
|
675
764
|
end
|
676
765
|
__doodle__.arg_order = args
|
677
766
|
rescue Exception => e
|
678
|
-
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s,
|
767
|
+
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, (caller)
|
679
768
|
end
|
680
769
|
else
|
681
770
|
__doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
|
682
771
|
end
|
683
772
|
end
|
684
773
|
|
685
|
-
# fixme: move
|
686
|
-
def get_init_values(tf = true)
|
687
|
-
__doodle__.attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
|
688
|
-
#p [:get_init_values, a.name]
|
689
|
-
hash[n] = case a.init
|
690
|
-
when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
|
691
|
-
# uncloneable values
|
692
|
-
#p [:get_init_values, :special, a.name, a.init]
|
693
|
-
a.init
|
694
|
-
when DeferredBlock
|
695
|
-
#p [:get_init_values, self, DeferredBlock, a.name]
|
696
|
-
begin
|
697
|
-
instance_eval(&a.init.block)
|
698
|
-
rescue Object => e
|
699
|
-
#p [:exception_in_deferred_block, e]
|
700
|
-
raise
|
701
|
-
end
|
702
|
-
else
|
703
|
-
#p [:get_init_values, :clone, a.name]
|
704
|
-
begin
|
705
|
-
a.init.clone
|
706
|
-
rescue Exception => e
|
707
|
-
warn "tried to clone #{a.init.class} in :init option"
|
708
|
-
#p [:get_init_values, :exception, a.name, e]
|
709
|
-
a.init
|
710
|
-
end
|
711
|
-
end
|
712
|
-
hash
|
713
|
-
}
|
714
|
-
end
|
715
|
-
private :get_init_values
|
716
|
-
|
717
774
|
# return true if instance variable +name+ defined
|
718
775
|
# fixme: move
|
719
776
|
def ivar_defined?(name)
|
@@ -734,7 +791,7 @@ class Doodle
|
|
734
791
|
##DBG: Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
|
735
792
|
else
|
736
793
|
attribs = __doodle__.attributes
|
737
|
-
##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes",
|
794
|
+
##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes", doodle.attributes] }
|
738
795
|
end
|
739
796
|
attribs.each do |name, att|
|
740
797
|
ivar_name = "@#{att.name}"
|
@@ -750,7 +807,7 @@ class Doodle
|
|
750
807
|
##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
|
751
808
|
break
|
752
809
|
elsif self.class != Class
|
753
|
-
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'",
|
810
|
+
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", (caller)
|
754
811
|
end
|
755
812
|
end
|
756
813
|
|
@@ -761,10 +818,10 @@ class Doodle
|
|
761
818
|
##DBG: Doodle::Debug.d { [:validate!, self, v ] }
|
762
819
|
begin
|
763
820
|
if !instance_eval(&v.block)
|
764
|
-
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }",
|
821
|
+
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", (caller)
|
765
822
|
end
|
766
823
|
rescue Exception => e
|
767
|
-
__doodle__.handle_error self, ValidationError, e.to_s,
|
824
|
+
__doodle__.handle_error self, ValidationError, e.to_s, (caller)
|
768
825
|
end
|
769
826
|
end
|
770
827
|
end
|
@@ -772,90 +829,6 @@ class Doodle
|
|
772
829
|
self
|
773
830
|
end
|
774
831
|
|
775
|
-
# turn off validation, execute block, then set validation to same
|
776
|
-
# state as it was before +defer_validation+ was called - can be nested
|
777
|
-
# fixme: move
|
778
|
-
def defer_validation(&block)
|
779
|
-
old_validation = __doodle__.validation_on
|
780
|
-
__doodle__.validation_on = false
|
781
|
-
v = nil
|
782
|
-
begin
|
783
|
-
v = instance_eval(&block)
|
784
|
-
ensure
|
785
|
-
__doodle__.validation_on = old_validation
|
786
|
-
end
|
787
|
-
validate!(false)
|
788
|
-
v
|
789
|
-
end
|
790
|
-
|
791
|
-
# helper function to initialize from hash - this is safe to use
|
792
|
-
# after initialization (validate! is called if this method is
|
793
|
-
# called after initialization)
|
794
|
-
# fixme?
|
795
|
-
def doodle_initialize_from_hash(*args)
|
796
|
-
#!p [:doodle_initialize_from_hash, :args, *args]
|
797
|
-
defer_validation do
|
798
|
-
# hash initializer
|
799
|
-
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
800
|
-
key_values, args = args.partition{ |x| x.kind_of?(Hash)}
|
801
|
-
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args] }
|
802
|
-
#!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
|
803
|
-
|
804
|
-
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
805
|
-
#init_values = get_init_values
|
806
|
-
#!p [:init_values, init_values]
|
807
|
-
|
808
|
-
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
809
|
-
#arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
|
810
|
-
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
811
|
-
#!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]
|
812
|
-
|
813
|
-
# merge all hash args into one
|
814
|
-
key_values = key_values.inject(arg_keywords) { |hash, item|
|
815
|
-
#!p [self.class, :doodle_initialize_from_hash, :merge, hash, item]
|
816
|
-
hash.merge(item)
|
817
|
-
}
|
818
|
-
#!p [self.class, :doodle_initialize_from_hash, :key_values2, key_values]
|
819
|
-
|
820
|
-
# convert keys to symbols (note not recursively - only first level == doodle keywords)
|
821
|
-
key_values.keys.each do |k|
|
822
|
-
sym_key = k.respond_to?(:to_sym) ? k.to_sym : k
|
823
|
-
key_values[sym_key] = key_values.delete(k)
|
824
|
-
end
|
825
|
-
|
826
|
-
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values2, key_values, :args2, args] }
|
827
|
-
#!p [self.class, :doodle_initialize_from_hash, :key_values3, key_values]
|
828
|
-
|
829
|
-
# create attributes
|
830
|
-
key_values.keys.each do |key|
|
831
|
-
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
|
832
|
-
#p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
|
833
|
-
if respond_to?(key)
|
834
|
-
__send__(key, key_values[key])
|
835
|
-
else
|
836
|
-
# raise error if not defined
|
837
|
-
__doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}", [caller[-1]]
|
838
|
-
end
|
839
|
-
end
|
840
|
-
# do init_values after user supplied values so init blocks can depend on user supplied values
|
841
|
-
#p [:getting_init_values, instance_variables]
|
842
|
-
init_values = get_init_values
|
843
|
-
init_values.each do |key, value|
|
844
|
-
if !key_values.key?(key) && respond_to?(key)
|
845
|
-
__send__(key, value)
|
846
|
-
end
|
847
|
-
end
|
848
|
-
end
|
849
|
-
end
|
850
|
-
#private :doodle_initialize_from_hash
|
851
|
-
|
852
|
-
# return containing object (set during initialization)
|
853
|
-
# (named doodle_parent to avoid clash with ActiveSupport)
|
854
|
-
# fixme: move
|
855
|
-
def doodle_parent
|
856
|
-
__doodle__.parent
|
857
|
-
end
|
858
|
-
|
859
832
|
# object can be initialized from a mixture of positional arguments,
|
860
833
|
# hash of keyword value pairs and a block which is instance_eval'd
|
861
834
|
def initialize(*args, &block)
|
@@ -866,8 +839,8 @@ class Doodle
|
|
866
839
|
__doodle__.validation_on = true
|
867
840
|
__doodle__.parent = Doodle.context[-1]
|
868
841
|
Doodle.context.push(self)
|
869
|
-
defer_validation do
|
870
|
-
|
842
|
+
__doodle__.defer_validation do
|
843
|
+
doodle.initialize_from_hash(*args)
|
871
844
|
instance_eval(&block) if block_given?
|
872
845
|
end
|
873
846
|
Doodle.context.pop
|
@@ -980,11 +953,14 @@ class Doodle
|
|
980
953
|
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
981
954
|
#DBG: Doodle::Debug.d { [:has, self, self.class, params] }
|
982
955
|
if !params.key?(:name)
|
983
|
-
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name",
|
956
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", (caller)
|
957
|
+
params[:name] = :__ERROR_missing_name__
|
984
958
|
else
|
985
|
-
|
959
|
+
# ensure that :name is a symbol
|
960
|
+
params[:name] = params[:name].to_sym
|
986
961
|
end
|
987
|
-
|
962
|
+
name = params[:name]
|
963
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", (caller) if positional_args.size > 0
|
988
964
|
|
989
965
|
if collector = params.delete(:collect)
|
990
966
|
if !params.key?(:using)
|
@@ -1014,6 +990,7 @@ class Doodle
|
|
1014
990
|
params[:collector_name] = collector_name
|
1015
991
|
end
|
1016
992
|
params[:doodle_owner] = owner
|
993
|
+
#p [:params, owner, params]
|
1017
994
|
params
|
1018
995
|
end
|
1019
996
|
end
|
@@ -1121,11 +1098,13 @@ class Doodle
|
|
1121
1098
|
def define_collection
|
1122
1099
|
if collector_class.nil?
|
1123
1100
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1101
|
+
junk = #{name} if !#{name} # force initialization for classes
|
1124
1102
|
args.unshift(block) if block_given?
|
1125
1103
|
#{name}.<<(*args);
|
1126
1104
|
end", __FILE__, __LINE__)
|
1127
1105
|
else
|
1128
1106
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1107
|
+
junk = #{name} if !#{name} # force initialization for classes
|
1129
1108
|
if args.size > 0 and args.all?{|x| x.kind_of?(#{collector_class})}
|
1130
1109
|
#{name}.<<(*args)
|
1131
1110
|
else
|
@@ -1155,12 +1134,14 @@ class Doodle
|
|
1155
1134
|
# need to use string eval because passing block
|
1156
1135
|
if collector_class.nil?
|
1157
1136
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1137
|
+
junk = #{name} if !#{name} # force initialization for classes
|
1158
1138
|
args.each do |arg|
|
1159
1139
|
#{name}[arg.send(:#{key})] = arg
|
1160
1140
|
end
|
1161
1141
|
end", __FILE__, __LINE__)
|
1162
1142
|
else
|
1163
1143
|
doodle_owner.sc_eval("def #{collector_name}(*args, &block)
|
1144
|
+
junk = #{name} if !#{name} # force initialization for classes
|
1164
1145
|
if args.size > 0 and args.all?{|x| x.kind_of?(#{collector_class})}
|
1165
1146
|
args.each do |arg|
|
1166
1147
|
#{name}[arg.send(:#{key})] = arg
|
@@ -1173,7 +1154,6 @@ class Doodle
|
|
1173
1154
|
end
|
1174
1155
|
end
|
1175
1156
|
end
|
1176
|
-
|
1177
1157
|
end
|
1178
1158
|
|
1179
1159
|
############################################################
|