module Virtus class Coercion # Coerce String values class String < Object primitive ::String TRUE_VALUES = %w[ 1 on t true y yes ].freeze FALSE_VALUES = %w[ 0 off f false n no ].freeze BOOLEAN_MAP = ::Hash[ TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze INTEGER_REGEXP = /[-+]?(?:0|[1-9]\d*)/.freeze EXPONENT_REGEXP = /(?:[eE][-+]?\d+)/.freeze FRACTIONAL_REGEXP = /(?:\.\d+#{EXPONENT_REGEXP}?)/.freeze NUMERIC_REGEXP = /\A(#{INTEGER_REGEXP}#{FRACTIONAL_REGEXP}?|#{FRACTIONAL_REGEXP})\z/.freeze # Coerce give value to a constant # # @example # Virtus::Coercion::String.to_constant('String') # => String # # @param [String] value # # @return [Object] # # @api public def self.to_constant(value) # TODO: add support for namespaced classes like 'Virtus::Attribute::String' ::Object.const_get(value) end # Coerce give value to a symbol # # @example # Virtus::Coercion::String.to_symbol('string') # => :string # # @param [String] value # # @return [Symbol] # # @api public def self.to_symbol(value) value.to_sym end # Coerce given value to Time # # @example # Virtus::Coercion::String.to_time(string) # => Time object # # @param [String] value # # @return [Time] # # @api public def self.to_time(value) parse_value(::Time, value) end # Coerce given value to Date # # @example # Virtus::Coercion::String.to_date(string) # => Date object # # @param [String] value # # @return [Date] # # @api public def self.to_date(value) parse_value(::Date, value) end # Coerce given value to DateTime # # @example # Virtus::Coercion::String.to_datetime(string) # => DateTime object # # @param [String] value # # @return [DateTime] # # @api public def self.to_datetime(value) parse_value(::DateTime, value) end # Coerce value to TrueClass or FalseClass # # @example with "T" # Virtus::Coercion::String.to_boolean('T') # => true # # @example with "F" # Virtus::Coercion::String.to_boolean('F') # => false # # @param [#to_s] # # @return [Boolean] # # @api public def self.to_boolean(value) BOOLEAN_MAP.fetch(value.downcase, value) end # Coerce value to integer # # @example # Virtus::Coercion::String.to_integer('1') # => 1 # # @param [Object] value # # @return [Integer] # # @api public def self.to_integer(value) # coerce to a Float first to evaluate scientific notation (if any) # that may change the integer part, then convert to an integer coerced = to_float(value) ::Float === coerced ? coerced.to_i : coerced end # Coerce value to float # # @example # Virtus::Coercion::String.to_float('1.2') # => 1.2 # # @param [Object] value # # @return [Float] # # @api public def self.to_float(value) to_numeric(value, :to_f) end # Coerce value to decimal # # @example # Virtus::Coercion::String.to_decimal('1.2') # => # # # @param [Object] value # # @return [BigDecimal] # # @api public def self.to_decimal(value) to_numeric(value, :to_d) end # Match numeric string # # @param [String] value # value to typecast # @param [Symbol] method # method to typecast with # # @return [Numeric] # number if matched, value if no match # # @api private def self.to_numeric(value, method) if value =~ NUMERIC_REGEXP $1.send(method) else value end end private_class_method :to_numeric # Parse the value or return it as-is if it is invalid # # @param [#parse] parser # # @param [String] value # # @return [Time] # # @api private def self.parse_value(parser, value) parser.parse(value) rescue ArgumentError return value end private_class_method :parse_value end # class String end # class Coercion end # module Virtus