lib/phonie/phone.rb in phonie-1.0.4 vs lib/phonie/phone.rb in phonie-2.0.0
- old
+ new
@@ -1,5 +1,9 @@
+require 'active_model/naming'
+require 'active_model/translation'
+require 'active_model/validations'
+
# An object representing a phone number.
#
# The phone number is recorded in 3 separate parts:
# * country_code - e.g. '385', '386'
# * area_code - e.g. '91', '47'
@@ -9,10 +13,12 @@
# Phone.default_country_code
# Phone.default_area_code
#
module Phonie
class Phone
+ EXTENSION = /[ ]*(ext|ex|x|xt|#|:)+[^0-9]*\(*([-0-9]{1,})\)*#?$/i
+
attr_accessor :country_code, :area_code, :number, :extension, :country
cattr_accessor :default_country_code
cattr_accessor :default_area_code
cattr_accessor :named_formats
@@ -27,115 +33,66 @@
:default_with_extension => "+%c%a%nx%x",
:europe => '+%c (0) %a %f %l',
:us => "(%a) %f-%l"
}
+ include ActiveModel::Validations
+ validates :country_code, :presence => true
+ validates :area_code, :presence => true
+ validates :number, :presence => true
+
def initialize(*hash_or_args)
if hash_or_args.first.is_a?(Hash)
hash_or_args = hash_or_args.first
keys = {:country => :country, :number => :number, :area_code => :area_code, :country_code => :country_code, :extension => :extension}
else
keys = {:number => 0, :area_code => 1, :country_code => 2, :extension => 3, :country => 4}
end
- self.number = hash_or_args[ keys[:number] ]
- self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
+ self.number = hash_or_args[ keys[:number] ]
+ self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
self.country_code = hash_or_args[ keys[:country_code] ] || self.default_country_code
- self.extension = hash_or_args[ keys[:extension] ]
- self.country = hash_or_args[ keys[:country] ]
-
- # Santity checks
- raise "Must enter number" if self.number.blank?
- raise "Must enter area code or set default area code" if self.area_code.blank?
- raise "Must enter country code or set default country code" if self.country_code.blank?
+ self.extension = hash_or_args[ keys[:extension] ]
+ self.country = hash_or_args[ keys[:country] ]
end
- def self.parse!(string, options={})
- parse(string, options.merge(:raise_exception_on_error => true))
+ def self.parse!(string, options = {})
+ pn = parse(string, options)
+ raise ArgumentError.new("#{string} is not a valid phone number") unless pn && pn.valid?
+ pn
end
# create a new phone number by parsing a string
# the format of the string is detect automatically (from FORMATS)
- def self.parse(string, options={})
- return nil unless string.present?
+ def self.parse(string, options = {})
+ return unless string.present?
- Country.load
+ options[:country_code] ||= self.default_country_code
+ options[:area_code] ||= self.default_area_code
extension = extract_extension(string)
normalized = normalize(string)
- options[:country_code] ||= self.default_country_code
- options[:area_code] ||= self.default_area_code
-
- parts = split_to_parts(normalized, options)
-
- pn = Phone.new(parts) if parts
- if pn.present? and extension.present?
- pn.extension = extension
- end
- pn
+ return unless country = Country.detect(normalized, options[:country_code], options[:area_code])
+ parts = country.parse(normalized, options[:area_code])
+ parts[:country] = country
+ parts[:country_code] = country.country_code
+ parts[:extension] = extension
+ new(parts)
end
# is this string a valid phone number?
def self.valid?(string, options = {})
- begin
- parse(string, options).present?
- rescue
- false # don't raise exceptions on parse errors
- end
+ pn = parse(string, options)
+ pn && pn.valid?
end
def self.is_mobile?(string, options = {})
pn = parse(string, options)
- return false if pn.nil?
- pn.is_mobile?
+ pn && pn.is_mobile?
end
- private
- # split string into hash with keys :country_code, :area_code and :number
- def self.split_to_parts(string, options = {})
- country = Country.detect(string, options[:country_code], options[:area_code])
-
- if country.nil?
- raise "Could not determine country" if options[:raise_exception_on_error]
- return nil
- end
-
- country.number_parts(string, options[:area_code])
- end
-
- # fix string so it's easier to parse, remove extra characters etc.
- def self.normalize(string_with_number)
- string_with_number.sub(extension_regex, '').gsub(/\(0\)|[^0-9+]/, '').gsub(/^00/, '+')
- end
-
- def self.extension_regex
- /[ ]*(ext|ex|x|xt|#|:)+[^0-9]*\(*([-0-9]{1,})\)*#?$/i
- end
-
- # pull off anything that look like an extension
- #
- def self.extract_extension(string)
- return nil if string.nil?
- if string.match extension_regex
- extension = $2
- return extension
- end
- #
- # We already returned any recognizable extension.
- # However, we might still have extra junk to the right
- # of the phone number proper, so just chop it off.
- #
- idx = string.rindex(/[0-9]/)
- return nil if idx.nil?
- return nil if idx == (string.length - 1) # at the end
- string.slice!((idx+1)..-1) # chop it
- return nil
- end
-
- public # instance methods
-
def area_code_long
"0" + area_code if area_code
end
# For many countries it's not apparent from the number
@@ -155,11 +112,11 @@
number[-n2_length, n2_length]
end
# Formats the phone number.
#
- # if the method argument is a String, it is used as a format string, with the following fields being interpolated:
+ # if the method argument is a String, it is used as a format string, with the following fields being interpolated:
#
# * %c - country_code (385)
# * %a - area_code (91)
# * %A - area_code with leading zero (091)
# * %n - number (5125486)
@@ -169,11 +126,11 @@
#
# if the method argument is a Symbol, it is used as a lookup key for a format String in Phone.named_formats
# pn.format(:europe)
def format(fmt)
if fmt.is_a?(Symbol)
- raise "The format #{fmt} doesn't exist'" unless named_formats.has_key?(fmt)
+ raise ArgumentError.new("The format #{fmt} doesn't exist") unless named_formats.has_key?(fmt)
format_number named_formats[fmt]
else
format_number(fmt)
end
end
@@ -199,17 +156,33 @@
methods.all? { |method| other.respond_to?(method) && send(method) == other.send(method) }
end
private
+ # split string into hash with keys :country_code, :area_code and :number
+ def self.split_to_parts(string, options = {})
+ country = Country.detect(string, options[:country_code], options[:area_code])
+ country && country.parse(string, options[:area_code])
+ end
+
+ # fix string so it's easier to parse, remove extra characters etc.
+ def self.normalize(string_with_number)
+ string_with_number.sub(EXTENSION, '').gsub(/\(0\)|[^0-9+]/, '').gsub(/^00/, '+')
+ end
+
+ # pull off anything that look like an extension
+ def self.extract_extension(string)
+ return unless string && string.match(EXTENSION)
+ Regexp.last_match[2]
+ end
+
def format_number(fmt)
- result = fmt.gsub("%c", country_code || "").
- gsub("%a", area_code || "").
- gsub("%A", area_code_long || "").
- gsub("%n", number || "").
- gsub("%f", number1 || "").
- gsub("%l", number2 || "").
- gsub("%x", extension || "")
- return result
+ fmt.gsub("%c", country_code || "").
+ gsub("%a", area_code || "").
+ gsub("%A", area_code_long || "").
+ gsub("%n", number || "").
+ gsub("%f", number1 || "").
+ gsub("%l", number2 || "").
+ gsub("%x", extension || "")
end
end
end