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
- #after_update(params) ⇒ Object
-
#arg_order(*args) ⇒ Object
define order for positional arguments.
-
#convert(owner, *args) ⇒ Object
convert a value according to conversion rules FIXME: move.
-
#datatypes(*mods) ⇒ Object
set up global datatypes.
-
#default?(name) ⇒ Boolean
return true if attribute has default defined and not yet been assigned to (i.e. still has default value).
-
#doc(*args, &block) ⇒ Object
(also: #doc=)
doc
add docs to doodle class or attribute. -
#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.
-
#from(*args, &block) ⇒ Object
if block passed, define a conversion from class if no args, apply conversion to arguments.
-
#has(*args, &block) ⇒ Object
has
is an extendedattr_accessor
. -
#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.
-
#kind(*args, &block) ⇒ Object
add a validation that attribute must be of class <= kind.
-
#marshal_dump ⇒ Object
helper for Marshal.dump.
-
#marshal_load(data) ⇒ Object
helper for Marshal.load.
-
#must(constraint = 'be valid', &block) ⇒ Object
add a validation.
-
#to_hash ⇒ Object
create ‘pure’ hash of scalars only from attributes - hacky but works (kinda).
-
#to_string_hash ⇒ Object
begin YAML::load(to_yaml.gsub(/!ruby/object:.*$/, ”)) || { } rescue Object => e doodle.attributes.inject({}) {|hash, (name, attribute)| hash = send(name); hash} end.
-
#validate(owner, *args) ⇒ Object
validate that args meet rules defined with
must
fixme: move. -
#validate!(all = true) ⇒ Object
validate this object by applying all validations in sequence - if all == true, validate all attributes, e.g.
Methods included from SmokeAndMirrors
Methods included from SelfClass
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.}", 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)
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_dump ⇒ Object
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_hash ⇒ Object
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_hash ⇒ Object
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.}", 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. } - 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. }", 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 |