ruby 41 lines · 7 steps

Wrapping phone parsing in a Ruby value object

A PhoneNumber class turns messy input into validated E.164 output by memoizing a single parse.

Explained by highlit
1require "phonelib"
2 
3class PhoneNumber
4 class InvalidNumber < StandardError; end
5 
6 attr_reader :raw, :default_region
7 
8 def initialize(raw, default_region: "US")
9 @raw = raw.to_s.strip
10 @default_region = default_region
11 end
12 
13 def self.normalize(raw, default_region: "US")
14 new(raw, default_region: default_region).e164
15 end
16 
17 def e164
18 raise InvalidNumber, "blank number" if raw.empty?
19 raise InvalidNumber, "#{raw.inspect} is not a valid phone number" unless parsed.valid?
20 
21 parsed.e164
22 end
23 
24 def valid?
25 !raw.empty? && parsed.valid?
26 end
27 
28 def country_code
29 parsed.country if valid?
30 end
31 
32 def type
33 parsed.types.first if valid?
34 end
35 
36 private
37 
38 def parsed
39 @parsed ||= Phonelib.parse(raw, default_region)
40 end
41end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Wrapping a third-party parser in your own object gives you a stable, domain-specific API.
  2. 2Memoizing an expensive call once and reusing it keeps every method cheap and consistent.
  3. 3A dedicated exception class lets callers distinguish invalid input from other failures.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Wrapping phone parsing in a Ruby value object — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code