Module: Doodle::BaseMethods

Includes:
SelfClass, SmokeAndMirrors
Defined in:
lib/doodle.rb

Overview

the core module of Doodle - however, to get most facilities provided by Doodle without inheriting from Doodle, include Doodle::Core, not this module

Instance Method Summary collapse

Methods included from SmokeAndMirrors

#inspect, #pretty_print

Methods included from SelfClass

#sc_eval, #singleton_class

Instance Method Details

#arg_order(*args) ⇒ Object

define order for positional arguments



757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/doodle.rb', line 757

def arg_order(*args)
  if args.size > 0
    begin
      args = args.uniq
      args.each do |x|
        __doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", (caller) if !(x.class <= Symbol)
        __doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", (caller) if !doodle.attributes.keys.include?(x)
      end
      __doodle__.arg_order = args
    rescue Exception => e
      __doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, (caller)
    end
  else
    __doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
  end
end

#convert(owner, *args) ⇒ Object

convert a value according to conversion rules fixme: move



620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/doodle.rb', line 620

def convert(owner, *args)
  #!p [:convert, 1, owner, args, __doodle__.conversions]
  begin
    args = args.map do |value|
      #!p [:convert, 2, value]
      if (converter = __doodle__.conversions[value.class])
        #!p [:convert, 3, value]
        value = converter[*args]
        #!p [:convert, 4, value]
      else
        #!p [:convert, 5, value]
        # try to find nearest ancestor
        this_ancestors = value.class.ancestors
        #!p [:convert, 6, this_ancestors]
        matches = this_ancestors & __doodle__.conversions.keys
        #!p [:convert, 7, matches]
        indexed_matches = matches.map{ |x| this_ancestors.index(x)}
        #!p [:convert, 8, indexed_matches]
        if indexed_matches.size > 0
          #!p [:convert, 9]
          converter_class = this_ancestors[indexed_matches.min]
          #!p [:convert, 10, converter_class]
          if converter = __doodle__.conversions[converter_class]
            #!p [:convert, 11, converter]
            value = converter[*args]
            #!p [:convert, 12, value]
          end
        else
          #!p [:convert, 13, :kind, kind, name, value] 
          mappable_kinds = kind.select{ |x| x <= Doodle::Core }
          #!p [:convert, 13.1, :kind, kind, mappable_kinds] 
          if mappable_kinds.size > 0
            mappable_kinds.each do |mappable_kind|
              #!p [:convert, 14, :kind_is_a_doodle, value.class, mappable_kind, mappable_kind.doodle.conversions, args] 
              if converter = mappable_kind.doodle.conversions[value.class]
                #!p [:convert, 15, value, mappable_kind, args] 
                value = converter[*args]
                break
              else
                #!p [:convert, 16, :no_conversion_for, value.class] 
              end
            end
          else
            #!p [:convert, 17, :kind_has_no_conversions]
          end
        end
      end
      #!p [:convert, 18, value]
      value
    end
  rescue Exception => e
    owner.__doodle__.handle_error name, ConversionError, "#{e.message}", (caller)
  end
  if args.size > 1
    args
  else
    args.first
  end
end

#datatypes(*mods) ⇒ Object

set up global datatypes



466
467
468
469
470
# File 'lib/doodle.rb', line 466

def datatypes(*mods)
  mods.each do |mod|
    DataTypeHolder.class_eval { include mod }
  end
end

#doodle(*mods, &block) ⇒ Object

vector through this method to get to doodle info or enable global datatypes and provide an interface that allows you to add your own datatypes to this declaration



475
476
477
478
479
480
481
482
483
484
485
# File 'lib/doodle.rb', line 475

def doodle(*mods, &block)
  if mods.size == 0 && !block_given?
    __doodle__
  else
    dh = Doodle::DataTypeHolder.new(self)
    mods.each do |mod|
      dh.extend(mod)
    end
    dh.instance_eval(&block)
  end
end

#from(*args, &block) ⇒ Object

if block passed, define a conversion from class if no args, apply conversion to arguments



579
580
581
582
583
584
585
586
587
588
589
# File 'lib/doodle.rb', line 579

def from(*args, &block)
  #p [:from, self, args]
  if block_given?
    # set the rule for each arg given
    args.each do |arg|
      __doodle__.local_conversions[arg] = block
    end
  else
    convert(self, *args)
  end
end

#has(*args, &block) ⇒ Object

has is an extended attr_accessor

simple usage - just like attr_accessor:

class Event
  has :date
end

set default value:

class Event
  has :date, :default => Date.today
end

set lazily evaluated default value:

class Event
  has :date do
    default { Date.today }
  end
end


742
743
744
745
746
747
748
749
750
751
752
753
754
# File 'lib/doodle.rb', line 742

def has(*args, &block)
  #DBG: Doodle::Debug.d { [:has, self, self.class, args] }
  
  params = DoodleAttribute.params_from_args(self, *args)
  # get specialized attribute class or use default
  attribute_class = params.delete(:using) || DoodleAttribute

  # could this be handled in DoodleAttribute?
  # define getter setter before setting up attribute
  define_getter_setter params[:name], *args, &block
  #p [:attribute, attribute_class, params]
  __doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
end

#initialize(*args, &block) ⇒ Object

object can be initialized from a mixture of positional arguments, hash of keyword value pairs and a block which is instance_eval’d



834
835
836
837
838
839
840
841
842
843
844
845
846
847
# File 'lib/doodle.rb', line 834

def initialize(*args, &block)
  built_in = Doodle::BuiltIns::BUILTINS.select{ |x| self.kind_of?(x) }.first
  if built_in
    super
  end
  __doodle__.validation_on = true
  __doodle__.parent = Doodle.context[-1]
  Doodle.context.push(self)
  __doodle__.defer_validation do
    doodle.initialize_from_hash(*args)
    instance_eval(&block) if block_given?
  end
  Doodle.context.pop
end

#kind(*args, &block) ⇒ Object

add a validation that attribute must be of class <= kind



602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/doodle.rb', line 602

def kind(*args, &block)
  @kind ||= []
  if args.size > 0
    @kind = [args].flatten
    # todo[figure out how to handle kind being specified twice?]
    if @kind.size > 2
      kind_text = "a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # => 
    else
      kind_text = "a kind of #{@kind.to_s}"
    end
    __doodle__.local_validations << (Validation.new(kind_text) { |x| @kind.any? { |klass| x.kind_of?(klass) } })
  else
    @kind
  end
end

#marshal_dumpObject

helper for Marshal.dump



488
489
490
491
# File 'lib/doodle.rb', line 488

def marshal_dump
  # note: perhaps should also dump singleton attribute definitions?
  instance_variables.map{|x| [x, instance_variable_get(x)] }
end

#marshal_load(data) ⇒ Object

helper for Marshal.load



493
494
495
496
497
# File 'lib/doodle.rb', line 493

def marshal_load(data)
  data.each do |name, value|
    instance_variable_set(name, value)
  end
end

#must(constraint = 'be valid', &block) ⇒ Object

add a validation



592
593
594
595
596
597
598
599
# File 'lib/doodle.rb', line 592

def must(constraint = 'be valid', &block)
  if block.nil?
    # is this really useful? do I really want it?
    __doodle__.local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
  else
    __doodle__.local_validations << Validation.new(constraint, &block)
  end
end

#validate(owner, *args) ⇒ Object

validate that args meet rules defined with must fixme: move



682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/doodle.rb', line 682

def validate(owner, *args)
  ##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
  #!p [:validate, 1, args]
  begin
    value = convert(owner, *args)
  rescue Exception => e
    owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } - #{e.message}", (caller)
  end
  #!p [:validate, 2, args, :becomes, value]
  __doodle__.validations.each do |v|
    ##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
    if !v.block[value]
      owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", (caller)
    end
  end
  #!p [:validate, 3, value]
  value
end

#validate!(all = true) ⇒ Object

validate this object by applying all validations in sequence

  • if all == true, validate all attributes, e.g. when loaded from YAML, else validate at object level only



783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
# File 'lib/doodle.rb', line 783

def validate!(all = true)
  ##DBG: Doodle::Debug.d { [:validate!, all, caller] }
  if all
    __doodle__.errors.clear
  end
  if __doodle__.validation_on
    if self.class == Class
      attribs = __doodle__.class_attributes
      ##DBG: Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
    else
      attribs = __doodle__.attributes
      ##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes", doodle.attributes] }
    end
    attribs.each do |name, att|
      ivar_name = "@#{att.name}"
      if instance_variable_defined?(ivar_name)
        # if all == true, reset values so conversions and
        # validations are applied to raw instance variables
        # e.g. when loaded from YAML
        if all
          ##DBG: Doodle::Debug.d { [:validate!, :sending, att.name, instance_variable_get(ivar_name) ] }
          __send__("#{att.name}=", instance_variable_get(ivar_name))
        end
      elsif att.optional?   # treat default/init as special case
        ##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
        break
      elsif self.class != Class
        __doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", (caller)
      end
    end
    
    # now apply instance level validations
    
    ##DBG: Doodle::Debug.d { [:validate!, "validations", doodle_validations ]}
    __doodle__.validations.each do |v|
      ##DBG: Doodle::Debug.d { [:validate!, self, v ] }
      begin
        if !instance_eval(&v.block)
          __doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", (caller)
        end
      rescue Exception => e
        __doodle__.handle_error self, ValidationError, e.to_s, (caller)
      end
    end
  end
  # if OK, then return self
  self
end