lib/strongly_typed/coercible.rb
require 'boolean_class'
require 'date'
module StronglyTyped
module Coercible
LOCAL_OFFSET = Float(Time.now.gmt_offset) / Float(3600)
# Coerce (convert) a value to some specified type
#
# @param [Object] value the value to coerce
# @param [Hash] opts the conversion options
# @option opts [Class, Module] :to the type to convert to
#
# @return [Object] the converted value into the specified type
#
# @example
# include StronglyTyped::Coercible
#
# coerce 100, to: Float #=> 100.0
# coerce 100 #=> ArgumentError: Needs option :to => Class/Module
# coerce 100, to: String #=> "100"
# coerce 100, to: Boolean #=> true
# coerce 100, to: Symbol #=> TypeError: can't convert `100:Fixnum` to `Symbol`
#
# @raise [ArgumentError] if :to => Class option was not provided correctly
# @raise [TypeError] if unable to perform the coersion
def coerce(value, opts={})
raise ArgumentError, "Needs option :to => Class/Module" unless opts.has_key?(:to) && ( opts[:to].is_a?(Class) || opts[:to].is_a?(Module) )
type = opts[:to]
case
# Direct conversions
when type <= String then String(value)
when type <= Boolean then Boolean(value)
when type == Bignum then raise TypeError, "directly converting to Bignum is not supported, use Integer instead"
when type <= Integer then Integer(value)
when type <= Float then Float(value)
when type <= Rational then Rational(value)
when type <= Complex then Complex(value)
# Symbol
when type <= Symbol && value.respond_to?(:to_sym)
value.to_sym
# Dates and Times
when type <= Time && value.is_a?(Numeric)
Time.at(value)
when type <= Time && value.is_a?(String)
DateTime.parse(value).new_offset(LOCAL_OFFSET/24).to_time
when type <= DateTime && value.respond_to?(:to_datetime)
value.to_datetime.new_offset(LOCAL_OFFSET/24)
when type <= DateTime && value.is_a?(String)
DateTime.parse(value).new_offset(LOCAL_OFFSET/24)
when type <= DateTime && value.is_a?(Integer)
DateTime.parse(value.to_s).new_offset(LOCAL_OFFSET/24)
# Important: DateTime < Date so the order in this case statement matters
when type <= Date && value.is_a?(String)
Date.parse(value)
when type <= Date && value.is_a?(Integer)
Date.parse(value.to_s)
else
raise TypeError, "can't convert `#{value}:#{value.class}` to `#{type}`"
end
end
end
end