lib/gorillib/receiver.rb in gorillib-0.1.1 vs lib/gorillib/receiver.rb in gorillib-0.1.2
- old
+ new
@@ -1,5 +1,9 @@
+require 'gorillib/object/blank'
+require 'gorillib/object/try'
+require 'gorillib/array/extract_options'
+
# dummy type for receiving True or False
class Boolean ; end unless defined?(Boolean)
# Receiver lets you describe complex (even recursive!) actively-typed data models that
# * are creatable or assignable from static data structures
@@ -83,23 +87,27 @@
RECEIVER_BODIES[Time] = %q{ v.nil? ? nil : Time.parse(v.to_s).utc rescue nil }
RECEIVER_BODIES[Date] = %q{ v.nil? ? nil : Date.parse(v.to_s) rescue nil }
RECEIVER_BODIES[Array] = %q{ case when v.nil? then nil when v.blank? then [] else Array(v) end }
RECEIVER_BODIES[Hash] = %q{ case when v.nil? then nil when v.blank? then {} else v end }
RECEIVER_BODIES[Boolean] = %q{ case when v.nil? then nil when v.to_s.strip.blank? then false else v.to_s.strip != "false" end }
- RECEIVER_BODIES[NilClass] = %q{ raise ArgumentError, "This field must be nil, but {#{v}} was given" unless (v.nil?) ; nil }
+ RECEIVER_BODIES[NilClass] = %q{ raise ArgumentError, "This field must be nil, but [#{v}] was given" unless (v.nil?) ; nil }
RECEIVER_BODIES[Object] = %q{ v } # accept and love the object just as it is
#
# Give each base class a receive method
#
RECEIVER_BODIES.each do |k,b|
- if k.is_a?(Class)
+ if k.is_a?(Class) && b.is_a?(String)
k.class_eval <<-STR, __FILE__, __LINE__ + 1
def self.receive(v)
#{b}
end
STR
+ elsif k.is_a?(Class)
+ k.class_eval do
+ define_singleton_method(:receive, &b)
+ end
end
end
TYPE_ALIASES = {
:null => NilClass,
@@ -185,16 +193,23 @@
# @option [Class] :of - For collections (Array, Hash, etc), the type of the collection's items
#
def rcvr name, type, info={}
name = name.to_sym
type = type_to_klass(type)
- class_eval <<-STR, __FILE__, __LINE__ + 1
+ body = receiver_body_for(type, info)
+ if body.is_a?(String)
+ class_eval(%Q{
def receive_#{name}(v)
- v = (#{receiver_body_for(type, info)}) ;
+ self.instance_variable_set("@#{name}", (#{body}))
+ end}, __FILE__, __LINE__ + 1)
+ else
+ define_method("receive_#{name}") do |*args|
+ v = body.call(*args)
self.instance_variable_set("@#{name}", v)
+ v
end
- STR
+ end
# careful here: don't modify parent's class_attribute in-place
self.receiver_attrs = self.receiver_attrs.dup
self.receiver_attr_names += [name] unless receiver_attr_names.include?(name)
self.receiver_attrs[name] = info.merge({ :name => name, :type => type })
end
@@ -252,27 +267,50 @@
defs[name] = info[:default] if info.has_key?(:default)
end
defs
end
+ # returns an in-order traversal of the
+ #
+ def tuple_keys
+ return @tuple_keys if @tuple_keys
+ @tuple_keys = self
+ @tuple_keys = receiver_attrs.map do |attr, info|
+ info[:type].try(:tuple_keys) || attr
+ end.flatten
+ end
+
+ def consume_tuple(tuple)
+ obj = self.new
+ receiver_attrs.each do |attr, info|
+ if info[:type].respond_to?(:consume_tuple)
+ val = info[:type].consume_tuple(tuple)
+ else
+ val = tuple.shift
+ end
+ # obj.send("receive_#{attr}", val)
+ obj.send("#{attr}=", val)
+ end
+ obj
+ end
+
protected
def receiver_body_for type, info
type = type_to_klass(type)
# Note that Array and Hash only need (and only get) special treatment when
# they have an :of => SomeType option.
case
when info[:of] && (type == Array)
- %Q{ v.nil? ? nil : v.map{|el| #{info[:of]}.receive(el) } }
+ receiver_type = info[:of]
+ lambda{|v| v.nil? ? nil : v.map{|el| receiver_type.receive(el) } }
when info[:of] && (type == Hash)
- %Q{ v.nil? ? nil : v.inject({}){|h, (el,val)| h[el] = #{info[:of]}.receive(val); h } }
+ receiver_type = info[:of]
+ lambda{|v| v.nil? ? nil : v.inject({}){|h, (el,val)| h[el] = receiver_type.receive(val); h } }
when Receiver::RECEIVER_BODIES.include?(type)
Receiver::RECEIVER_BODIES[type]
when type.is_a?(Class)
- %Q{v.blank? ? nil : #{type}.receive(v) }
- # when (type.is_a?(Symbol) && type.to_s =~ /^[A-Z]/)
- # # a hack so you can use a class not defined yet
- # %Q{v.blank? ? nil : #{type}.receive(v) }
+ lambda{|v| v.blank? ? nil : type.receive(v) }
else
raise("Can't receive #{type} #{info}")
end
end
@@ -284,10 +322,22 @@
else raise ArgumentError, "Can\'t handle type #{type}: is it a Class or one of the TYPE_ALIASES?"
end
end
end
+ def to_tuple
+ tuple = []
+ self.each_value do |val|
+ if val.respond_to?(:to_tuple)
+ tuple += val.to_tuple
+ else
+ tuple << val
+ end
+ end
+ tuple
+ end
+
module ClassMethods
# By default, the hashlike methods iterate over the receiver attributes.
# If you want to filter our add to the keys list, override this method
#
# @example
@@ -301,15 +351,17 @@
end
# set up receiver attributes, and bring in methods from the ClassMethods module at class-level
def self.included base
base.class_eval do
- class_attribute :receiver_attrs
- class_attribute :receiver_attr_names
- class_attribute :after_receivers
- self.receiver_attrs = {} # info about the attr
- self.receiver_attr_names = [] # ordered set of attr names
- self.after_receivers = [] # blocks to execute following receive!
- extend ClassMethods
+ unless method_defined?(:receiver_attrs)
+ class_attribute :receiver_attrs
+ class_attribute :receiver_attr_names
+ class_attribute :after_receivers
+ self.receiver_attrs = {} # info about the attr
+ self.receiver_attr_names = [] # ordered set of attr names
+ self.after_receivers = [] # blocks to execute following receive!
+ extend ClassMethods
+ end
end
end
end