
Partager:
Karl est un défenseur des développeurs pour Vonage, qui se concentre sur la maintenance de nos SDK de serveur Ruby et sur l'amélioration de l'expérience des développeurs pour notre communauté. Il aime apprendre, fabriquer des objets, partager ses connaissances et tout ce qui a trait à la technologie du web.
Numbers de téléphone dans Rails avec Normalizes et Generated Columns
Temps de lecture : 12 minutes
Si vous créez une application qui utilise les API de Vonage Communications APIsde Vonage Communications, vous devrez probablement, à un moment ou à un autre, travailler avec des numéros de téléphone d'une manière ou d'une autre. Que votre application effectue des appels téléphoniques automatisés par synthèse vocale via l Voice APIou d'envoyer des SMS ou des messages WhatsApp à l'aide de l'API Messages APIou qu'elle mette en œuvre une authentification à deux facteurs avec l'API Verify APIvous devrez spécifier un numéro de téléphone de destination pour ces interactions.
Numéros de téléphone internationaux
Comme on peut s'y attendre, les points de terminaison de l'API exigent que les numéros de téléphone soient dans un format spécifique et cohérent. Le format format de numéro utilisé par les API de Vonage suit la norme internationale norme internationale E.164et s'attend à ce que :
Un indicatif international de pays (par ex.
1pour les États-Unis ou44pour le Royaume-Uni)Pour le code d'accès principal
+ou le code d'accès international (par exemple00) soit omisPour que le code d'accès à la ligne (par ex.
0) soit omisPour le nombre lui-même, à l'exclusion de tout caractère spécial (par ex.
()ou-)
Par exemple, un numéro américain aura le format 14155550101et un numéro britannique aurait le format 447700900123.
Lors de la création d'applications Web, la source des données relatives au numéro de téléphone est généralement constituée par les données saisies par l'utilisateur dans le cadre d'un processus d'inscription. S'assurer que les données saisies sont dans le bon format pour une utilisation ultérieure avec l'API externe peut poser quelques problèmes. Si vous créez une application Ruby on Rails, quelques fonctionnalités récemment ajoutées peuvent vraiment vous aider à relever ces défis.
Exemple de flux de travail
Avant de nous pencher sur ces fonctionnalités, réfléchissons un peu plus sur le processus d'inscription. Dans une application Ruby on Rails, nous définirons probablement un modèle User et un UsersControllerainsi que les modèles de vue nécessaires. Notre modèle users/new une fois rendu, peut ressembler à quelque chose comme ceci :
Rendered Rails 'users/new' view template screenshot
Notez que dans cet exemple, le code pays et le numéro de téléphone sont des champs de formulaire distincts, et donc, dans une implémentation Rails standard, ils seront persistés dans la base de données en tant qu'attributs distincts de notre modèle. Par exemple, nous pourrions nous attendre à ce que notre UsersController contienne du code du type suivant :
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
Nous Nous pourrions combiner les deux données en une seule entrée dans notre formulaire, mais cela augmenterait la responsabilité de l'utilisateur quant à la saisie correcte des données et rendrait potentiellement plus difficile le nettoyage des données en arrière-plan. Le fait que le code du pays du téléphone soit un champ de formulaire à part entière nous permet de limiter les données à un ensemble spécifique de valeurs, par exemple à l'aide d'un menu déroulant. Il y a plusieurs façons d'implémenter ce champ, que je ne détaillerai pas ici, mais ne manquez pas de consulter la page de la countries gem et country_select gem.
Dans une application Rails, nous utiliserons probablement des aides de formulaire Rails dans nos modèles de vue, et dans le cas du champ Numéro de téléphone lui-même, il s'agira probablement de l'outil telephone_field aide (ou son alias, phone_field). Dans la vue rendue, cette aide crée un élément HTML <input> avec un élément avec un attribut type dont la valeur est tel. Hormis la valeur de type (qui peut être utilisée par les navigateurs de téléphones portables pour déterminer si un clavier numérique doit être affiché), ce champ est fonctionnellement équivalent à un champ de type text (en fait, dans les navigateurs qui ne prennent pas en charge les champs de type telce champ sera remplacé par un champ de type text standard).
Contrairement à d'autres types d'entrées (par ex. email ou url), les navigateurs ne prennent pas en charge la validation automatique des entrées pour ce type de champ. Nous pouvons bien sûr fournir des conseils de formatage à l'utilisateur à côté du champ de saisie, et nous pourrions peut-être utiliser l'attribut pattern l'attribut sur l'élément afin de limiter la saisie ou d'utiliser une solution basée sur JavaScript. En tant que développeurs responsables, nous ne devrions cependant pas complètement Il est toujours prudent de nettoyer les données saisies par l'utilisateur avant de les transférer dans une base de données. Dans le contexte de la saisie d'un numéro de téléphone, et en particulier lorsqu'il s'agit de soutenir une clientèle internationale, cela signifie qu'il faut être prêt à faire face à une grande variété une grande variété de formats de numéros potentiels. Vous trouverez ci-dessous quelques exemples de formats de numéros de téléphone utilisés dans le monde :
77 12 3000
012345-60000
(01234) 500000
06-10000000
0123-400-000
01234/500000-67C'est ici que Ruby on Rails normalizes méthode peut s'avérer très utile.
Normalise
La méthode normalizes a été ajoutée dans Rails 7.1. La méthode est incluse dans ActiveRecord::Base via le module Normalization et est donc disponible pour tous nos modèles Rails qui héritent de la classe abstraite ApplicationRecord . Dans son utilisation, la méthode nécessite une liste d'args d'un ou plusieurs :names (qui correspondent aux attributs que vous souhaitez normaliser) et un argument de type mot-clé :with dont la valeur est un lambda Ruby, l'argument du lambda représentant l'attribut à normaliser.
Voici à quoi pourrait ressembler l'utilisation de normalize pourrait ressembler si elle était appliquée à notre scénario de numéro de téléphone :
class User < ApplicationRecord
normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("0") }
end
Dans l'exemple ci-dessus, :phone est une chaîne de caractères, donc dans la lambda nous utilisons deux méthodes de la classe Ruby String pour transformer la valeur d'entrée :
La
String#deleteméthode. Cette méthode renvoie une copie de l'objet chaîne de caractères sur lequel elle a été appelée, en supprimant tous les caractères indiqués par l'argument sélecteur. Ici, grâce à l'utilisation de l'astérisque^le sélecteur indique que tous les caractères pas dans l'intervalle0-9doivent être supprimés (c'est-à-dire tous les caractères non numériques).La
String#delete_prefixméthode. Similaire à la méthodedeletecette méthode renvoie une copie de l'objet chaîne de caractères sur lequel elle a été appelée, mais dans ce cas, elle ne supprime que les caractères du début de la chaîne qui correspondent à la chaîne passée en argument. Ici, elle supprimera le caractère'0'(s'il est présent) de la chaîne renvoyée par l'invocation de la méthodedelete.
Avec l normalizes ajoutée à notre classe User nous pouvons rapidement tester notre implémentation dans la console Rails, en utilisant la méthode normalize_value_for méthode:
$ 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"
Il était possible d'effectuer ce type de transformation des données avant Rails 7.1 et l'ajout de la méthode normalizes mais la mise en œuvre aurait été un peu plus complexe.
Une option consisterait à utiliser un Active Record callback tel que before_save:
class User < ApplicationRecord
before_save :sanitize_phone
private
def sanitize_phone
phone&.delete("^0-9")&.delete_prefix("0")
end
end
Une autre option consisterait à passer outre la méthode setter :
class User < ApplicationRecord
def phone=(value)
super(value&.delete("^0-9")&.delete_prefix("0"))
end
end
L'approche normalizes est cependant plus simple et plus propre à mettre en œuvre, en particulier si vous devez transformer plusieurs attributs de la même manière :
class User < ApplicationRecord
normalizes :home_phone, :work_phone, with: -> phone { phone.delete("^0-9").delete_prefix("0") }
end
Avec les deux autres approches, nous devrions remplacer plusieurs fixateurs ou définir plusieurs méthodes d'aide.
Notez également l'utilisation de l'opérateur de navigation sûre de Ruby opérateur de navigation sécurisé de Ruby & dans ces autres implémentations. Cet opérateur indique à Ruby de sauter un appel de méthode si l'appelant est nilet son utilisation ici permet d'éviter qu'un NoMethodError dans une situation où l'attribut :phone était nil. En raison du fonctionnement de l'opérateur de navigation sécurisée, nous devons nous rappeler de l'inclure pour chaque dans la chaîne. Avec normalizesnous obtenons ce comportement gratuitement par défaut. En d'autres termes, si :phone est nilla lambda ne sera pas exécutée du tout.
Mises en garde
Quelques mises en garde concernant notre exemple normalizes l'implémentation :
Il ne couvre pas toutes les toutes les situations ou tous les cas limites que vous pourriez rencontrer en production. Par exemple, la partie
delete_prefix("0")est présent parce que de nombreux pays ajoutent un0à leurs numéros de téléphone comme code d'accès lors de la composition d'un numéro à l'intérieur du pays (connu sous le nom d'accès au réseau), mais cela n'est pas nécessaire lors de la composition d'un numéro à l'étranger (lorsqu'un code de sortie international est utilisé devant le numéro).0n'est pas nécessaire pour les appels internationaux (un code de sortie international est alors utilisé devant le code international du pays de destination).Malheureusement, tous les pays ne suivent pas la même approche. Tout d'abord, tous les pays n'exigent pas un code d'accès au réseau. Parmi ceux qui le font, la plupart utilisent
0mais un nombre important de pays (dont les États-Unis) utilisent également1comme code d'accès au réseau. Il y a également d'autres aberrations : quelques pays utilisent8la Hongrie utilise06le Mexique utilise01et la Mongolie utilise01ou02.L'Italie (ainsi que Saint-Marin et la Cité du Vatican) est un cas encore plus délicat. avait l'habitude utilisait
0comme préfixe d'accès au réseau interurbain, mais qui inclut désormais inclut ce0comme partie intégrante du numéro pour à la fois interne et et la Grèce qui utilise2pour les lignes fixes et6pour les Numbers mobiles. Pour traiter tous ces cas particuliers, une approche plus robuste pour notrenormalizespour notre implémentation en production pourrait être d'utiliser de manière conditionnelle différents en fonction de l'indicatif de pays saisi.L'objectif de
normalizesest de s'assurer que vos données sont conformes à un format spécifique avant de les transférer dans une base de données. Ce qu'il ne c'est de vérifier si le numéro est valide et/ou digne de confiance. Pour ce faire, vous pouvez envisager d'intégrer l'API Number Insight API de Vonage de Vonage dans le cadre de votre processus d'inscription (bien que cela dépasse le cadre de cet article).
Pour les besoins de cet article, supposons que nous sommes satisfaits de nos données à ce stade. Maintenant que nous avons nos données, comment pouvons-nous l'utiliser les utiliser ? Cela nous amène à la deuxième fonctionnalité de Ruby on Rails que je souhaite aborder : Les colonnes générées.
Colonnes générées
Les données relatives à nos numéros de téléphone sont stockées, mais :phone_country_code et :phone sont des attributs distincts de notre modèle User modèle. La migration de notre modèle User pourrait ressembler à ceci :
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
Lors de la création ou de la mise à jour d'un Usernous définissons et accédons à ces attributs séparément :
user = User.new(phone_country_code: "44", phone: "7900000001")
user.phone_country_code # => "44"
user.phone # => "7900000001"
Quand l'utilisation de ces données avec les API de Vonage Communications, par exemple pour envoyer un SMS à l'aide de l'API Messagesnous devons combiner le code du pays et le numéro de téléphone :
message = Vonage::Messaging::Message.sms(message: "Hello from Vonage!")
Vonage.messaging.send(
to: "447900000001",
from: "Vonage",
**message
)(Note : L'exemple ci-dessus utilise le Vonage Ruby SDK de Vonage via l'initialisateur Initialisateur Vonage Rails)
Une option consisterait à combiner les données au point d'utilisation :
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
)L'inconvénient de cette approche est que nous pourrions utiliser ces données combinées à plusieurs endroits dans notre base de code, et qu'il faudrait répéter ce schéma avec chaque utilisation des données. Si nous voulons conserver notre code DRY, une meilleure approche consisterait à fournir un moyen d'accéder aux données déjà combinées, par exemple sous la forme de 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
)Une façon de procéder consisterait à définir une méthode dans notre modèle qui combine les deux valeurs. User qui combine les deux valeurs, par exemple :
class User < ApplicationRecord
# rest of User code
def international_phone_number
phone_country_code + phone
end
end
Plutôt que de définir des méthodes d'obtention supplémentaires dans notre classe User il serait plus propre d'interroger les données directement à partir de notre base de données.
Avant Rails 7.0, une façon d'y parvenir était de définir une colonne international_phone_number dans notre tableau users puis d'utiliser un before_save qui crée un attribut supplémentaire sur l'objet, et calcule sa valeur, avant de la persister dans la base de données :
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
Nous pourrions alors interroger cette colonne chaque fois que nécessaire :
user = User.create!(phone_country_code: "44", phone: "7900000001")
user.international_phone_number => "447900000001"
Outre le sentiment d'être un peu "bricolé", cette approche présente d'autres inconvénients :
Il ajoute un code de type "boiler-plate" à notre
UserclasseNous avons du code dans notre classe
Userqui définit en partieinternational_phone_numberalors qu'idéalement, nous devrions conserver ce type de logique dans nosActiveRecordmigrations.
Depuis Rails 7.0, il existe une solution plus propre à ce problème : les colonnes générées. Une colonne générée est une sorte de colonne virtuelle dont la valeur est dérivée de la valeur d'autres colonnes. Contrairement à l'exemple précédent, toute la logique des colonnes générées est contenue au niveau de la base de données.
Ce concept n'est pas nouveau. MySQL dispose de colonnes virtuelles depuis MySQL 5.7, et plusieurs autres SGBDR ont des fonctionnalités équivalentes. Cependant, si vous construisez une application Rails, il y a de fortes chances que vous utilisiez PostgreSQL pour la couche de persistance des données. PostgreSQL a ajouté Colonnes générées dans PostgreSQL 12.0et la version Rails 7.0.0 a mis à jour l'adaptateur Rails PostgreSQL pour les supporter.
Voyons comment nous pouvons utiliser les colonnes générées dans le cadre de notre mise en œuvre.
Pour ajouter une colonne international_phone_number générée à notre table users nous devons créer une migration pour la définir :
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
Les trois premiers arguments de la méthode add_column méthode sont les arguments standards requis pour cette méthode particulière dans une migration Rails. Nous avons un table_name de :users (qui est la table que nous voulons mettre à jour), un argument column_name de :international_phone_numberet un type de :virtual. Ces éléments sont suivis d'un options avec trois clés spécifiques à la définition de la colonne :virtual colonne :
:type. Il s'agit du type de données de la colonne:virtualdans ce cas, il s'agit d'un:string:as. Cela permet de spécifier la manière dont la valeur de la colonne doit être générée. Dans ce cas, nous concaténons les valeursphone_country_codeetphoneen utilisant l'opérateur||de PostgreSQL.:stored. Il s'agit d'une valeur booléenne qui détermine si la valeur de la colonne doit être calculée lorsque les données sont saisies.:virtualdoit être calculée lorsque les données sont écrites dans les colonnes utilisées pour les générer (true), ou lorsque la colonne virtuelle elle-même est interrogée (false).
Une mise en garde concernant l'utilisation de colonnes générées est que les colonnes à partir desquelles la valeur de la colonne générée est dérivée doivent être définies sur la même table que la colonne générée elle-même.
Conclusion
Comme nous l'avons vu, l'utilisation des données relatives aux numéros de téléphone dans nos Applications peut présenter des difficultés, notamment lorsque des formats internationaux spécifiques sont requis pour ces données. Cependant, en tant que développeurs Ruby on Rails, nous disposons de nombreuses fonctionnalités utiles, telles que la méthode normalizes et les colonnes générées, qui peuvent rendre notre code plus propre et nous faciliter la vie.
Peut-être utiliserez-vous l'une de ces fonctionnalités dans votre prochaine application Rails pour développer quelque chose de génial avec les API de Vonage Communications. Si c'est le cas, vous pouvez vous inscrire à un compte gratuit de Account du développeur de Vonage pour commencer à construire tout de suite. Si vous avez des questions ou si vous voulez simplement discuter, n'hésitez pas à nous rejoindre sur Twitter ou sur le Slack des développeurs de Vonage. Bon codage et à la prochaine fois !
Partager:
Karl est un défenseur des développeurs pour Vonage, qui se concentre sur la maintenance de nos SDK de serveur Ruby et sur l'amélioration de l'expérience des développeurs pour notre communauté. Il aime apprendre, fabriquer des objets, partager ses connaissances et tout ce qui a trait à la technologie du web.