This is loosely based on "How to convert a String to Integer or Float".
If I wanted to convert a numerical string input to its "most appropriate type" using Ruby's built-in conversion mechanics, I could do something like this:
def convert(input)
  value = Integer(input) rescue nil
  value ||= Float(input) rescue nil
  value ||= Rational(input) rescue nil
  value
end
convert('1')     #=> 1
convert('1_000') #=> 1000
convert('0xff')  #=> 255
convert('0.5')   #=> 0.5
convert('1e2')   #=> 100.0
convert('1/2')   #=> (1/2)
convert('foo')   #=> nil
But this brute-force method-calling looks dirty. Is there a more elegant way to approach this? Can I check whether a value is a valid input for Integer(), Float() or Rational() so I could call these methods in a more controlled manner?
Using a trailing rescue makes me cringe as it can obscure problems with the underlying code since it traps Exception not ArgumentError, which is what the failed attempted conversions would raise. This isn't as concise but it'd handle the appropriate exception:
def convert(input)
  value = begin
            Integer(input)
          rescue ArgumentError
            nil
          end
  value ||= begin
              Float(input)
            rescue ArgumentError
              nil
            end
  value ||= begin
              Rational(input)
            rescue ArgumentError
              nil
            end
  value
end
convert('1')     # => 1
convert('1_000') # => 1000
convert('0xff')  # => 255
convert('0.5')   # => 0.5
convert('1e2')   # => 100.0
convert('1/2')   # => (1/2)
convert('foo')   # => nil
After thinking about it a bit it seems like that can be DRY'd down to:
def convert(input)
  [:Integer, :Float, :Rational].each do |m| 
    begin
      return Kernel.method(m).call(input)
    rescue ArgumentError
    end
  end
  nil
end
convert('1')     # => 1
convert('1_000') # => 1000
convert('0xff')  # => 255
convert('0.5')   # => 0.5
convert('1e2')   # => 100.0
convert('1/2')   # => (1/2)
convert('foo')   # => nil
As pointed out by Jörn, the above wasn't a good example. I was using Kernel to get at Integer(), Float() and Rational because that's where they're defined, but really Object was the place to look since it inherits from Kernel. 
And it'd been one of those days when I knew there was a good way to call the method indirectly, but call was sticking in my mind, not send as Stephan pointed out. So, here's a cleaner way of doing it, starting with:
  return Object.send(m, input)
But, that could be reduced to:
 return send(m, input)
resulting in:
def convert(input)
  [:Integer, :Float, :Rational].each do |m| 
    begin
      return send(m, input)
    rescue ArgumentError
    end
  end
  nil
end
convert('1')     # => 1
convert('1_000') # => 1000
convert('0xff')  # => 255
convert('0.5')   # => 0.5
convert('1e2')   # => 100.0
convert('1/2')   # => (1/2)
convert('foo')   # => nil
                        Since for some reason you prefer "0.3" to be converted to 3e-1 rather than to 3/10, this might be done in more explicit manner. After all, under the hood, there is the same recognition mechanism in ruby parser:
def convert input
  raise unless String === input && input[/\A_|_\z|__/].nil?
  input = input.strip.delete('_')
  case input
  when /\A-?\d+\z/ then Integer(input)
  when /\A-?0x[\da-f]+\z/i then Integer(input)
  when /\A-?(\d*\.)?\d+(e-?\d+)?\z/i then Float(input)
  when /\A-?(\d*\.)?\d+(e-?\d+)?\/\d+\z/i then Rational(input)
  end
end
And that works as expected :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With