elgalu/strongly_typed

View on GitHub
lib/strongly_typed/coercible.rb

Summary

Maintainability
A
1 hr
Test Coverage
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