doodle 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  ############################################################