Formatting a phone number

I have a program that will format a phone number in this format: (###)###-####

def obtain_phone_number
loop do
print 'Enter phone number: '
phone_num = gets.chomp
if !(phone_num[/\d{10}/])
puts 'Invalid phone number, must contain 10 digits only'
else
formatted_phone_num = phone_num.insert(0, '(').insert(4, ')').insert(8, '-')
return formatted_phone_num
end
end
end


When run:

irb(main):013:0> obtain_phone_number
Enter phone number: 1234567894
=> "(123)456-7894"
irb(main):014:0> obtain_phone_number
Enter phone number: 7894561234
=> "(789)456-1234"
irb(main):015:0> obtain_phone_number
Enter phone number: 5555555555
=> "(555)555-5555"


This works and all, and is great, but are there better ways to format a phone number? Something that's not as ugly as phone_num.insert(0, '(').insert(4, ')').insert(8, '-'). This just seems like it could be done in a more simplistic and easier way, and not to mention it will allow more than 10 digits.

You're already using a regex to check that the phone number is 10 digits, but you could also make it more precise, and use it to split the string apart:

def obtain_phone_number
loop do
print "Enter phone number: "
phone_num = gets.chomp
if phone_num =~ /^(\d{3})(\d{3})(\d{4})$/ return "(#{$1})#{$2}-#{$3}"
else
puts "Invalid phone number, must contain 10 digits only"
end
end
end


When the =~ matching runs, it assigns the capture groups to the global $1, $2 etc. variables, so it's easy to glue a string back together from those parts. You can fiddle with it to make it accept more than 10 digits, if you want.

However, I would move the loop somewhere else, and just have the method return a formatted string or nil. Or possibly string, nil or raise an exception. If you get nil back, the user entered nothing (e.g. user cancelled/left it blank). If it raises an error, the user did input something, but it was invalid, and you can warn and retry.

Right now, if you call the method, you can never escape it without entering a number. I don't know the usage context for this, but I'd imagine it'd make sense to allow the user to skip somehow, rather than forcing them to input nonsense just to proceed.

So something like this might be better:

def obtain_phone_number
phone_num = gets.chomp
return nil if phone_num.empty?
if phone_num =~ /^(\d{3})(\d{3})(\d{4})$/ "(#{$1})#{$2}-#{$3}"
else
raise "Invalid input"
end
end

# simple usage example
begin
print "Enter phone number: "
phone = obtain_phone_number
puts phone
rescue StandardError
puts "Invalid phone number, must contain 10 digits only"
retry
end