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

#after_update(params) ⇒ Object



721
722
# File 'lib/doodle.rb', line 721

def after_update(params)
end

#arg_order(*args) ⇒ Object

define order for positional arguments



976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
# File 'lib/doodle.rb', line 976

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", Doodle::Utils.doodle_caller if !(x.class <= Symbol)
        __doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", Doodle::Utils.doodle_caller if !doodle.attributes.keys.include?(x)
      end
      __doodle__.arg_order = args
    rescue Exception => e
      __doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, Doodle::Utils.doodle_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



829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
# File 'lib/doodle.rb', line 829

def convert(owner, *args)
  #pp( { :convert => 1, :owner => owner, :args => args, :conversions => __doodle__.conversions } )
  begin
    args = args.map do |value|
      #!p [:convert, 2, value]
      if (converter = __doodle__.conversions[value.class])
        #p [:convert, 3, value, self, caller]
        value = converter[value]
        #!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[value]
            #!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[value]
                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}", Doodle::Utils.doodle_caller
  end
  if args.size > 1
    args
  else
    args.first
  end
end

#datatypes(*mods) ⇒ Object

set up global datatypes



630
631
632
633
634
# File 'lib/doodle.rb', line 630

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

#default?(name) ⇒ Boolean

return true if attribute has default defined and not yet been assigned to (i.e. still has default value)

Returns:

  • (Boolean)


1002
1003
1004
# File 'lib/doodle.rb', line 1002

def default?(name)
  doodle.attributes[name.to_sym].optional? && !ivar_defined?(name)
end

#doc(*args, &block) ⇒ Object Also known as: doc=

doc add docs to doodle class or attribute



930
931
932
933
934
935
936
# File 'lib/doodle.rb', line 930

def doc(*args, &block)
  if args.size > 0
    @doc = *args
  else
    @doc
  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



639
640
641
642
643
644
645
646
647
648
649
# File 'lib/doodle.rb', line 639

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



794
795
796
797
798
799
800
801
802
803
804
# File 'lib/doodle.rb', line 794

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


961
962
963
964
965
966
967
968
969
970
971
972
973
# File 'lib/doodle.rb', line 961

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], params, &block
  #p [:attribute, attribute_class, params]
  attr = __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



1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
# File 'lib/doodle.rb', line 1059

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
  #p [:doodle_parent, Doodle.parent, caller[-1]]
  Doodle.context.push(self)
  __doodle__.defer_validation do
    doodle.initialize_from_hash(*args)
    instance_eval(&block) if block_given?
  end
  Doodle.context.pop
  #p [:doodle, __doodle__.__inspect__]
  #p [:doodle, __doodle__.attributes]
  #p [:doodle_parent, __doodle__.parent]
end

#kind(*args, &block) ⇒ Object

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



812
813
814
815
816
817
818
819
820
821
822
823
824
825
# File 'lib/doodle.rb', line 812

def kind(*args, &block)
  if args.size > 0
    @kind = [args].flatten
    # todo[figure out how to handle kind being specified twice?]
    if @kind.size > 2
      kind_text = "be a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # =>
    else
      kind_text = "be 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



652
653
654
655
# File 'lib/doodle.rb', line 652

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



657
658
659
660
661
# File 'lib/doodle.rb', line 657

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



807
808
809
# File 'lib/doodle.rb', line 807

def must(constraint = 'be valid', &block)
  __doodle__.local_validations << Validation.new(constraint, &block)
end

#to_hashObject

create ‘pure’ hash of scalars only from attributes - hacky but works (kinda)



1078
1079
1080
1081
1082
1083
1084
1085
# File 'lib/doodle.rb', line 1078

def to_hash
  Doodle::Utils.symbolize_keys!(YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }, true)
  #begin
  #  YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }
  #rescue Object => e
  #  doodle.attributes.inject({}) {|hash, (name, attribute)| hash[name] = send(name); hash}
  #end
end

#to_string_hashObject

begin

YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }

rescue Object => e

doodle.attributes.inject({}) {|hash, (name, attribute)| hash[name] = send(name); hash}

end



1086
1087
1088
# File 'lib/doodle.rb', line 1086

def to_string_hash
  Doodle::Utils.stringify_keys!(YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }, true)
end

#validate(owner, *args) ⇒ Object

validate that args meet rules defined with must fixme: move



891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
# File 'lib/doodle.rb', line 891

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}", Doodle::Utils.doodle_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 })", Doodle::Utils.doodle_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



1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
# File 'lib/doodle.rb', line 1008

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 && !att.readonly
          ##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 ]}
        next
      elsif self.class != Class
        __doodle__.handle_error name, Doodle::ValidationError, "#{self.kind_of?(Class) ? self : self.class } missing required attribute '#{name}'", Doodle::Utils.doodle_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 }", Doodle::Utils.doodle_caller
        end
      rescue Exception => e
        __doodle__.handle_error self, ValidationError, e.to_s, Doodle::Utils.doodle_caller
      end
    end
  end
  # if OK, then return self
  self
end