doodle 0.1.7 → 0.1.8
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/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
|
############################################################
|