libphonenumberをrailsに導入して電話番号を自在に扱う

libphonenumberについて

電話番号のバリデーション関連ライブラリをGoogleが公開したもの。Googleの2段階認証プロセスなどで導入されているらしい。

番号のパターンからフリーダイヤルか携帯かなど種別も判定することができる。 実態はPhoneNumberMetadata.xmlを始めとする正規表現メタデータを元に判定している様子。

libphonenumber関連記事

ruby on railsでlibphonenumberを使うにあたって

今のところ公式にサポートされている言語はJava, C++, JavaScriptのみだが、libphonenumberベースのruby gemは非公式ながらも存在する。

どちらのGemもPhoneNumberMetadata.xmlをMarshal.dumpしたdatファイルをgem内に有している。

で、どっちを使えばいいの?

libphonenumberの基本的な機能はどちらのGemでも大体サポートしている

ロケーションや番号種別ごとのバリデーション

irb(main):020:0> TelephoneNumber.valid?('03-1234-5678', :jp)
=> true

# Phonelib.parse('03-1234-5678', :jp).valid? でもいける
irb(main):018:0> Phonelib.valid_for_country?('03-1234-5678', :jp)
=> true

任意の番号ルールの拡張

標準の番号ルールを拡張して、独自のブラックリストなりを作ることも可能そう。

TelephoneNumber.override_file = '/path/to/override_phone_data.dat'

Phonelib.override_phone_data = '/path/to/override_phone_data.dat'

固定電話番号からのジオコーディング

irb(main):008:0> TelephoneNumber.parse('03-1234-5678', :jp).location
=> "Tokyo"

irb(main):009:0> Phonelib.parse('03-1234-5678', :jp).geo_name
=> "Tokyo"

電話番号からの種別判定

irb(main):051:0> TelephoneNumber.parse('0120123456', :jp).valid_types
=> [:toll_free]

irb(main):029:0> Phonelib.parse('0120123456', :jp).types
=> [:toll_free]

電話番号のE164フォーマット化

irb(main):054:0> TelephoneNumber.parse('0120123456', :jp).e164_number
=> "+81120123456"
irb(main):055:0> Phonelib.parse('0120123456', :jp).e164
=> "+81120123456"

Phonelibでのみサポートされている機能

ActiveRecord Integration

modelのvalidatesにもこんな感じでそのまま書ける。validatorクラスとか作りたくない人用かな。

validates :attribute, phone: { possible: true, allow_blank: true, types: [:voip, :mobile], country_specifier: -> phone { phone.country.try(:upcase) } }

Phonelibを使うことにした

  • telephone_numberと比較して提供されてる機能が豊富
  • libphonenumberの最新のメタデータに追従できている
  • commit頻度が高くcontributorも多い安心感

railsのmodelにバリデーションを適用する

やりたいこと

  • 電話番号のフォーマット違反の検知
    • これは Phonelib.parse(value, :jp).valid? などでいける
  • 受け入れたくない電話番号種別の検知
    • Phonelib.parse(value, :jp).types でvalidした電話番号種別が取れるので、良しなに弾く

受け入れる番号種別はホワイトリストではなくブラックリストで運用した方が良さそう

電話番号の種別はこれからも増えることが予想され、そういった場合ホワイトリストだと意図しないものまで弾いてしまいかねない。

よって確実に通したくないものを追記する運用で考える。

今回は明らかに個人ではないと思われる電話番号種別を弾いてみる。

実装

# frozen_string_literal: true
class TelephoneValidator < ActiveModel::EachValidator
  REJECTABLE_TYPES = %i(toll_free uan premium_rate pager).freeze

  def validate_each(record, attribute, value)
    parsed_tel = Phonelib.parse(value, :jp)
    return unless parsed_tel.invalid? || parsed_tel.types.any? { |type| REJECTABLE_TYPES.include?(type) }

    record.errors[attribute] << '無効な電話番号です'
  end
end

あとはmodelに

# frozen_string_literal: true
class User < ApplicationRecord
  validates :tel, telephone: true
end

みたいな感じで適用して終わり!

[49] pry(main)> User.new(tel: '08012345678').valid?
=> true

# 080は11桁であるべきなのでinvalid
[50] pry(main)> User.new(tel: '080123456789').valid?
=> false

# 0120は許可してない種別(フリーダイヤル)なのでinvalid
[51] pry(main)> User.new(tel: '0120123456').valid?
=> false