https://d226lax1qjow5r.cloudfront.net/blog/blogposts/phone-numbers-in-rails-with-normalizes-and-generated-columns/phone-numbers_rails.png

正規化と生成カラムを使用したRailsの電話番号

最終更新日 January 11, 2024

所要時間:2 分

を使用するアプリケーションを構築している場合 Vonage Communications APIを使用するアプリケーションを構築している場合、何らかの形で電話番号を扱うことになるでしょう。あなたのアプリケーションが Voice APIを使った SMS や WhatsApp メッセージの送信であれ メッセージ APIを使った二要素認証の実装、あるいは Verify APIで二要素認証を実装する場合は、これらのやり取りで送信先の電話番号を指定する必要があります。

国際電話番号

ご想像の通り、APIエンドポイントは電話番号が特定の一貫したフォーマットであることを要求する。VonageのAPIで使用される VonageのAPIで使用される番号フォーマットE.164 国際標準に従っています:

  • 国際国番号(例えば 1米国または 44英国)

  • 先頭の +または国際アクセスコード(例 00)を省略する

  • トランクアクセスコード(例えば 0)を省略する

  • 特殊文字(たとえば ()または -)

例えば、米国の電話番号は次のような書式になります。 14155550101イギリスの番号は 447700900123.

ウェブアプリケーションを構築する際の典型的なシナリオは、サインアップワークフローの一部として取り込まれたユーザー入力を電話番号データのソースとすることです。入力データが後で外部APIで使用するために正しい形式であることを保証することは、いくつかの課題をもたらす可能性があります。しかし、Ruby on Railsアプリケーションを構築している場合、最近追加されたいくつかの機能が、このような課題への対処に大いに役立ちます。

ワークフロー例

これらの機能がどのようなものかを説明する前に、サインアップのワークフローについてもう少し考えてみましょう。Ruby on Railsアプリケーションでは、おそらく Userモデルと UsersControllerを定義します。私たちの users/newテンプレートは、レンダリングされると次のようになります:

Screenshot of a rendered Rails 'users/new' view template showing an input form with First Name, Last Name, Email, Phone Country Code, and Phone Number input fieldsRendered Rails 'users/new' view template screenshot

この例では、Phone Country CodeとPhone Numberは別々のフォームフィールドなので、標準的なRails実装ではモデルの別々の属性としてデータベースに永続化されることに注意してください。たとえば UsersControllerには次のようなコードが含まれることが期待されます:

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)

    # rest of create action code
  end

  private

  def user_params
    params.require(:user).permit(:first_name, :last_name, :email, :phone_country_code, :phone)
  end
end

我々は を組み合わせることもできます。しかし、そうすると、ユーザーがデータを正しく入力する責任が重くなり、バックエンドでデータをクリーンアップするのが難しくなる可能性があります。電話の国コードをフォームフィールドとして持つことで、セレクトメニューなどでデータを特定の値に制限することができます。このフィールドを実装する方法はいくつかありますが、ここでは詳しく説明しません。 countries ジェムcountry_select gem.

Railsアプリケーションでは、ビューテンプレートでRailsフォームヘルパーを使うことになるでしょう。 telephone_field ヘルパー(またはそのエイリアス、 phone_field).レンダリングされたビューでは、このヘルパーがHTMLの <input> 要素を作成します。 type属性を持つHTML要素を作成します。 tel.の値を持つHTML要素を作成します。 type(の値(携帯電話ブラウザが数字キーボードが表示されるべきかどうかを判断するのに使われる)以外は、このフィールドは機能的に text型フィールドと機能的に同等です(実際、. telをサポートしていないブラウザでは、標準の text型入力に戻る)。

他の入力タイプ(例えば emailurlなど)と異なり、ブラウザはこのフィールドタイプに対する自動入力バリデーションをサポートしていません。もちろん、入力フィールドと一緒にユーザーに書式のヒントを提供することはできます。 pattern 属性属性を使用するか、JavaScript ベースのソリューションを使用することもできます。しかし、責任ある開発者として、私たちは 完全にデータベースに永続化する前に、ユーザー入力をクリーンアップするのが常に賢明です。電話番号の入力という文脈では、特にグローバルな顧客ベースをサポートする場合、これはつまり 多種多様な入力される可能性のある番号形式に備えることを意味します。以下は、世界中で使用されているさまざまな電話番号フォーマットのほんの一例です:

77 12 3000
012345-60000
(01234) 500000
06-10000000
0123-400-000
01234/500000-67

ここでRuby on Railsの normalizes メソッドが非常に有用です。

ノーマライズ

このメソッドは normalizesメソッドは で追加されました。.このメソッドは ActiveRecord::Baseモジュール経由で Normalizationモジュールに含まれているため、このメソッドを継承するすべてのRailsモデルで利用できます。 ApplicationRecord抽象クラスを継承するすべてのRailsモデルで利用できます。このメソッドを使うには、argsリストに1つ以上の :names(これは正規化したい属性に相当します)と :withキーワード引数が必要です。この引数の値はRubyラムダで、ラムダの引数は正規化する属性を表します。

以下は normalizeを電話番号のシナリオに当てはめると次のようになる:

class User < ApplicationRecord
  normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("0") }
end

上の例では :phoneは文字列なので、ラムダ内でRubyの Stringクラスの2つのメソッドを使って入力値を変換している:

  • その String#delete 方法.このメソッドは、呼び出された文字列オブジェクトのコピーを、セレクタ引数で指定された文字をすべて取り除いた状態で返します。ここでは、キャレット ^セレクタは でないでないすべての文字を 0-9の範囲にないすべての文字(つまり、数字以外のすべての文字)が削除されることを示しています。

  • その String#delete_prefix 方法.メソッドと似ている。 deleteメソッドと同様に、このメソッドは呼び出された文字列オブジェクトのコピーを返します。 開始文字を削除します。ここでは '0'(存在する場合)を削除します。 deleteメソッドによって返された文字列からその文字を削除します。

この normalizes呼び出しが Userクラスが追加されたので、Railsコンソールで実装をすぐにテストできるようになりました。 normalize_value_for メソッド:

$ rails console
Loading development environment (Rails 7.1.0)
3.1.0 :001 > User.normalize_value_for(:phone, "07900-00-00-00")
=> "7900000000"
3.1.0 :002 > User.normalize_value_for(:phone, "07900 000 000")
=> "7900000000"
3.1.0 :002 > User.normalize_value_for(:phone, "(07900) 000/000")
=> "7900000000"

このようなデータ変換を行うことは可能だった。 以前はメソッドが追加されるまでは、この種のデータ変換を行うことは可能でした。 normalizesメソッドが追加される前は、この種のデータ変換を行うことは可能でしたが、実装はもう少し複雑になっていたでしょう。

オプションの1つは アクティブレコードコールバックを使うこともできる。 before_save:

class User < ApplicationRecord
  before_save :sanitize_phone

  private

  def sanitize_phone
    phone&.delete("^0-9")&.delete_prefix("0")
  end
end

もうひとつの選択肢は、セッター・メソッドをオーバーライドすることだ:

class User < ApplicationRecord
  def phone=(value)
    super(value&.delete("^0-9")&.delete_prefix("0"))
  end
end

しかし normalizesしかし、特に複数のアトリビュートを同じように変換する必要がある場合は、このアプローチの方が実装がシンプルですっきりしている:

class User < ApplicationRecord
  normalizes :home_phone, :work_phone, with: -> phone { phone.delete("^0-9").delete_prefix("0") }
end

他の2つのアプローチでは、複数のセッターをオーバーライドするか、複数のヘルパー・メソッドを定義する必要がある。

また、Rubyの 安全なナビゲーション演算子 &を使用していることにも注意。これは、呼び出し元が nilである場合にメソッド呼び出しをスキップさせるものです。 NoMethodError属性が :phone属性が nil.安全なナビゲーション演算子の仕組み上、この演算子を忘れずに を含むことを忘れないようにする必要がある。メソッド呼び出しに含める必要があります。しかし normalizesを使えば、デフォルトでこの振る舞いを無料で利用できる。つまり :phonenilであれば、ラムダはまったく実行されない。

注意事項

この例に関するいくつかの注意事項 normalizesを実装する必要があります:

  1. すべての すべての本番で遭遇する可能性のあるすべての状況やエッジケースをカバーしているわけではありません。例えば delete_prefix("0")の部分がありますが、これは、多くの国で、国内でダイヤルするときに、アクセスコードとして電話番号の前に 0を電話番号の前に付けてアクセスコードとする国が多いからです(トランクアクセスとして知られています)。 0は国際電話をかけるときには必要ありません(国際電話をかける相手国の国番号の前に国際電話終了コードを使用する場合)。

    残念ながら、すべての国が同じ方法をとっているわけではありません。まず、すべての国がトランクアクセスコードを要求しているわけではありません。ほとんどの国では 0しかし、かなりの数の国(米国を含む)が、トランクアクセスコードとして 1をトランクアクセスコードとして使っている国もかなりあります。を使用している国もあります。 8ハンガリーは 06メキシコ 01モンゴルは 01または 02.

    イタリア(サンマリノ、バチカン市国と同様)は、さらに厄介なエッジケースである。 以前は以前は 0をトランク・アクセス・プレフィックスとして使用していたが、現在は を含むイタリア 0をNumbersの一部として使用するようになった。 内部 ギリシャでは固定電話と国際電話 2固定電話には 6を使うギリシャがある。このようなエッジケースに対処するために、私たちの実装では、よりロバストな normalizesを条件付きで使用することである。 異なるラムダを使うことである。

  2. の目的は normalizesの目的は、データをデータベースに永続化する前に、データが特定のフォーマットに準拠していることを確認することです。しかし しないのはが行わないのは、その番号が有効かどうか、あるいは信頼できるかどうかをチェックすることである。そのためには Vonage番号 Insight APIをサインアップワークフローの一部として統合することを検討するとよいだろう(この記事の範囲を超えているが)。

この記事の目的上、現時点でのデータに満足していると仮定しよう。さて、データが手に入ったところで、どうやって 使うのか?使うことができるでしょうか。そこで2つ目のRuby on Railsの機能を紹介します:生成カラムです。

生成されたコラム

電話番号のデータは保存されているが :phone_country_code:phoneUserモデルの別属性です。私たちの Userモデルのマイグレーションは次のようになります:

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
      t.string :email
      t.string :phone_country_code
      t.string :phone
      t.timestamps
    end
  end
end

を作成または更新するとき Userを作成または更新するときは、それらの属性を別々に設定し、アクセスする:

user = User.new(phone_country_code: "44", phone: "7900000001")

user.phone_country_code # => "44"
user.phone # => "7900000001"

そのデータを そのデータをを Vonage Communications API で使用する場合。 Messages API を使って SMS を送信する。を使用してSMSを送信するには を組み合わせる必要があります。国番号と電話番号を組み合わせる必要があります:

message = Vonage::Messaging::Message.sms(message: "Hello from Vonage!")

Vonage.messaging.send(
  to: "447900000001",
  from: "Vonage",
  **message
)

(注: 上記の例では Vonage Ruby SDKを使用しています。 Vonage Railsイニシャライザ)

ひとつの選択肢は、使用時にデータを組み合わせることだろう:

user = User.find_by(id: 1)
message = Vonage::Messaging::Message.sms(message: "Hello from Vonage!")

Vonage.messaging.send(
  to: "#{user.phone_country_code}#{user.phone}",
  from: "Vonage",
  **message
)

この方法の欠点は、コードベース内の複数の場所でこの結合データを使用する可能性があり、各コードベースでこのパターンを繰り返す必要があることです。 このパターンをを繰り返す必要がある。コードをDRYに保ちたいのであれば、すでに結合されたデータにアクセスする方法を提供するのがよいだろう。 international_phone_number.

user = User.find_by(id: 1)
message = Vonage::Messaging::Message.sms(message: "Hello from Vonage!")

Vonage.messaging.send(
  to: user.international_phone_number,
  from: "Vonage",
  **message
)

これを行う一つの方法は、例えば Userメソッドを定義することだ:

class User < ApplicationRecord
  # rest of User code

  def international_phone_number
    phone_country_code + phone
  end
end

クラスに追加のゲッター・メソッドを定義するよりも Userクラスで追加のゲッター・メソッドを定義するよりも、どうにかしてデータベースから直接データを照会したほうがすっきりするかもしれない。

Rails 7.0以前では、これを実現する1つの方法は international_phone_numberカラムを定義し usersテーブルにカラムを定義し before_saveコールバックを使ってオブジェクトに追加の属性を作成し、その値を計算してからデータベースに永続化します:

class User < ApplicationRecord
  before_save :set_international_phone_number

  private

  def set_international_phone_number
    self[:international_phone_number] = phone_country_code + phone
  end
end

そうすれば、必要なときにいつでもそのカラムに問い合わせることができる:

user = User.create!(phone_country_code: "44", phone: "7900000001")
user.international_phone_number => "447900000001"

やや "ハチャメチャ "に感じられるだけでなく、このアプローチには他にも欠点がある:

  • これは Userクラス

  • クラスには Userを部分的に定義している。 international_phone_number理想的には、この種のロジックは ActiveRecordを定義するコードがあります。

Rails 7.0以降では、この問題をすっきりと解決する方法として生成カラムがあります。生成カラムは 仮想カラムの一種で、その値は他のカラムの値から派生します。上の例とは対照的に、生成カラムのロジックはすべてデータベースレベルに含まれます。

このコンセプトは新しいものではない。MySQLにはMySQL 5.7から仮想カラムがあり、他のいくつかのRDBMSにも同等の機能があります。しかし、Railsアプリケーションを構築するのであれば、データ永続層でPostgreSQLを使う可能性が高いでしょう。PostgreSQLには 生成カラムを追加しました。 PostgreSQL 12.0Rails 7.0.0リリースでRails PostgreSQLアダプタが更新されました。

生成カラムを実装の一部として使用する方法を見てみましょう。

既存のカラムに international_phone_number生成カラムを既存の usersカラムを定義するマイグレーションを作る必要があります:

class AddInternationalPhoneNumberToUsers < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :international_phone_number, :virtual, type: :string,
      as: "phone_country_code || phone", stored: true
  end
end

メソッドへの最初の3つの位置引数 add_column メソッドへの最初の3つの位置引数は、Railsマイグレーションで特定のメソッドに要求される標準的な引数です。私たちは table_name:users(これは更新したいテーブルです)、メソッド呼び出しの最初の引数である column_name:international_phone_numberおよび type:virtual.に続く。 optionsハッシュが続きます。 :virtualカラムの定義に固有の3つのキーを持つハッシュが続く:

  • :type.これは :virtualカラムのデータ型であり、この場合は :string

  • :as.これは、カラムの値をどのように生成するかを指定します。この場合は phone_country_codeフィールドと phoneフィールドをPostgreSQLの ||演算子を使用しています。

  • :stored.これはブール値で :virtualカラムの値を計算するかどうかを決定するブール値です。 データを生成するためにカラムにデータが書き込まれたとき (true)、あるいは仮想カラム自体が クエリ(false).

生成されたカラムを使用する際の注意点として、生成されたカラムの値の元となるカラムは、同じテーブルに定義されていなければならないということがあります。 カラムと同じテーブルで定義されていなければならないことです。

結論

これまで見てきたように、アプリケーションで電話番号データを使用する場合、特にそのデータに特定の国際フォーマットが要求される場合には、困難が伴うことがあります。しかし、Ruby on Railsの開発者には normalizesメソッドやGenerated Columnsのような便利な機能がたくさん用意されています。

もしかしたら、次のRailsアプリでこれらの機能のいずれかを使って、Vonage Communications APIを使って何か素晴らしいものを開発するかもしれません。その場合は、無料の Vonage デベロッパーアカウントにサインアップしてください。ご質問がある場合、または単にチャットしたい場合は、お気軽にご参加ください。 ツイッターまたは VonageデベロッパーSlack.それではまた次回!

シェア:

https://a.storyblok.com/f/270183/373x376/e8d3211236/karl-lingiah.png
Karl LingiahRuby開発者支援

KarlはVonageのDeveloper Advocateで、RubyサーバSDKのメンテナンスとコミュニティの開発者エクスペリエンスの向上に注力しています。彼は学ぶこと、ものを作ること、知識を共有すること、そして一般的にウェブ技術に関連することが大好きです。