Number Validation with Regex in Ruby

Motivation

Proper implementation of input validation is one of the most fundamental aspects of any web application. While we could rely on some of the existing methods to achieve many types of input validation, some methods might not be as flexible as we would want them to be.

For instance, Object#is_a? method allows us to check if its calling object is of certain type:

>> 12.is_a? Integer
=> true
>> 'foo'.is_a? String
=> true
>> [1, 2, 3].is_a? Array
=> false

This method works well until we introduce objects that look like a number, an array, etc:

>> '12'.is_a? Integer
=> false
>> '[1, 2, 3]'.is_a? Array
=> false

The reason they return false , of course, is that these calling objects are actually strings rather than an integer or an array.

I’m not sure if these situations often arise in the wild but, if they did, we would want to avoid these inputs being treated as strings since we would be interested in validating what’s “inside” the strings.

Wouldn’t it be nice if we had a method that could have (almost) any types of arguments as an input, look at the “inside value” of these inputs, and verify if they are of certain type we specify? — that’s what I had in my mind when I decided to write this post.

Implementation

The implementation described below focuses on number validation. More precisely, it will check if the “content” of a given input of an arbitrary type belongs to one of the following types (classes): Integer, Fixnum.

def number?(obj)
obj = obj.to_s unless obj.is_a? String
/\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end

Let’s start by analysing the regex:

/\A[+-]?\d+(\.\d+)?\z/

Here is the list of ‘vocabulary’ included in the above regex.

/         start of the regex
\A start of the string to be matched
[+-]? zero or one of '+' or'-'
\d+ one or more of digit
(\.\d+)? zero or one of 'one dot and 'one or more of digit''
\z end of the string to be matched
/ end of the regex

(For more details on regex syntax, you might want to check this regex quick reference.)

Let’s hop into irb to verify the regex actually works:

(irb)
REGEX = /\A[+-]?\d+(\.[\d]+)?\z/
REGEX.match '13'
=> #<MatchData "13" 1:nil>
!!REGEX.match '13'
=> true
REGEX.match '3.14'
=> #<MatchData "3.14" 1:".14">
!!REGEX.match '3.14'
=> true
REGEX.match 'not a number'
=> nil
!!REGEX.match 'not a number'
=> false

If the inspected string represents a number, Regexp#match method returns a MatchData object, which evaluates to true. Otherwise it returns nil, which evaluates to false.

Please note here that we are trying to match String objects against the regex. If you try to match other types of object, the code throws an error:

(irb)
REGEX = /\A[+-]?\d+(\.[\d]+)?\z/
REGEX.match 13
=> TypeError: no implicit conversion of Fixnum into String ...
REGEX.match 3.14
=> TypeError: no implicit conversion of Float into String ...

In order to avoid this we will add the following code (*):

obj.to_s unless obj.is_a? String                                 (*)

(“obj” is the object we want to test against the regex) This line of code transforms an object into a String object if it’s not of type String and ensures that it will indeed be a String object when tested against the regex.

This completes the explanation as to how the above method:

def number?(obj)
obj = obj.to_s unless obj.is_a? String
/\A[+-]?\d+(\.[\d]+)?\z/.match obj
end

works.

Here is an example of how we can use this method (in this example, the code(*) will not be necessary as ‘number’ will always be of type String after being assigned a value via gets.chomp):

puts "Enter a number:"
number = nil
loop do
number = gets.chomp
break if number?(number)
puts "That is not a number."
end
puts "#{number} is indeed a number."

Running this code prints out the following:

Enter a number:
foo
That is not a number.
bar
That is not a number.
‘12.34’
That is not a number.
12.3.4
That is not a number.
12.34
12.34 is indeed a number.

References

You might also find the following resources on this subject helpful:

http://stackoverflow.com/questions/1235863/test-if-a-string-is-basically-an-integer-in-quotes-using-ruby

http://rubular.com/ (Interactive Ruby regex editor)


If you liked the post, don’t forget to give it some “claps” and follow me on social media :)

If there are areas which you think can be improved, please let me know in the comment section below. Any feedback will be greatly appreciated!