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.
@@ -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
@@ -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 = (class << self; self; end)
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, :doodle_local_attributes)
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.doodle_attributes)
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?(:doodle_attributes) ? @this.singleton_class.doodle_attributes : { })
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(:doodle_local_validations)
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, :doodle_local_conversions)
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", [caller[-1]]
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 = args.first
547
- __doodle__.local_validations << (Validation.new("be #{@kind}") { |x| x.class <= @kind })
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
- ancestors = value.class.ancestors
568
- #!p [:convert, 6, ancestors]
569
- matches = ancestors & __doodle__.conversions.keys
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| ancestors.index(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 = ancestors[indexed_matches.min]
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, "#{owner.kind_of?(Class) ? owner : owner.class} - #{e.to_s}", [caller[-1]]
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, :before_conversion, args]
601
- value = convert(owner, *args)
602
- #!p [:validate, :after_conversion, args, :becomes, value]
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 })", [caller[-1]]
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", [caller[-1]] if !(x.class <= Symbol)
674
- __doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", [caller[-1]] if !doodle_attributes.keys.include?(x)
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, [caller[-1]]
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", doodle_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}'", [caller[-1]]
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 }", [caller[-1]]
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, [caller[-1]]
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
- doodle_initialize_from_hash(*args)
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", [caller[-1]]
956
+ __doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", (caller)
957
+ params[:name] = :__ERROR_missing_name__
984
958
  else
985
- name = params[:name].to_sym
959
+ # ensure that :name is a symbol
960
+ params[:name] = params[:name].to_sym
986
961
  end
987
- __doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", [caller[-1]] if positional_args.size > 0
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
  ############################################################