Skip to content

Instantly share code, notes, and snippets.

@bensheldon
Created May 23, 2025 23:10
Show Gist options
  • Save bensheldon/e29fbd33f222eba0b48fa62544797277 to your computer and use it in GitHub Desktop.
Save bensheldon/e29fbd33f222eba0b48fa62544797277 to your computer and use it in GitHub Desktop.
A Phone Number tiny type
# frozen_string_literal: true
class PhoneNumber
EXACT_DIGITS = 10
MAX_NON_DIGITS = 5
delegate :present?, :blank?, :encoding, to: :@value
delegate :hash, to: :strict
def initialize(value)
@value = value
end
# Format: +1112223333 or unaltered string
def strict
normalize(@value)
end
# Strict format or raise ArgumentError
def strict!
valid? ? strict : raise(ArgumentError, "Invalid strict phone number: #{@value}")
end
# In strict format already
def valid?
normalize(@value)&.match?(/\A\+\d{11}\z/)
end
def loose_valid?
return false if @value.blank?
digits = @value.to_s.gsub(/\D/, '')
non_digits = @value.to_s.gsub(/\d/, '')
digits.size == EXACT_DIGITS && non_digits.size <= MAX_NON_DIGITS
end
# 555-555-5555
def display
return @value unless valid?
strict.to_s.delete_prefix('+1').insert(6, '-').insert(3, '-')
end
def ==(other)
strict == if other.is_a?(PhoneNumber)
other.strict
else
normalize(other)
end
end
def presence
present? ? self : nil
end
def to_s
@value
end
# Allow comparison of "string" == PhoneNumber
alias to_str to_s
private
# Turn into +1112223333, nil if blank, or unmodified value
def normalize(string)
return nil if string.blank?
digits = string.to_s.strip.gsub(/\D/, '')
if digits.size == 10
"+1#{digits}"
elsif digits.size == 11 && digits.start_with?('1')
"+#{digits}"
else
string
end
end
class Attribute < ActiveRecord::Type::Text
def cast(value)
return value if value.is_a?(PhoneNumber)
return nil if value.nil?
PhoneNumber.new(value)
end
def serialize(value)
if value.present?
value.is_a?(PhoneNumber) ? value.strict! : PhoneNumber.new(value).strict!
end
end
def deserialize(value)
value.nil? ? nil : PhoneNumber.new(value)
end
def changed_in_place?(raw_old_value, new_value)
raw_old_value != serialize(new_value)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment