[RUBY] [Rails / ActiveRecord] Je souhaite valider la valeur avant la conversion du type (_before_type_cast)

introduction

Dans ActiveRecord, si vous placez une chaîne de caractères dans une colonne de type Date ou de type Integer, elle sera automatiquement convertie en ce type de colonne.

Par exemple, si vous disposez du tableau des utilisateurs suivant: (Created_at et updated_at sont omis pour plus de simplicité.)

   create_table "users", force: :cascade do |t|
    t.string "name"
    t.integer "age"
  end

Essayons de saisir différentes valeurs à l'aide de la console Rails.

 irb (main): 001: 0> User.new (nom: "Taro", âge: 21) Essayez de mettre un nombre dans #age
 => # <Nom d'utilisateur: "Taro", âge: 21>

 irb (main): 002: 0> User.new (nom: "Taro", age: "21") Essayez de mettre une chaîne de caractères dans #age
 => # <Nom d'utilisateur: "Taro", âge: 21>

 irb (main): 003: 0> User.new (nom: "Taro", age: "21") Essayez de mettre une chaîne de caractères dans #age (1 est un nombre pleine largeur)
 => # <Nom d'utilisateur: "Taro", âge: 2>

Le premier est une valeur numérique. Puisqu'il s'agit d'une valeur numérique, elle peut être saisie telle quelle.

Le second est une chaîne. La chaîne de caractères "21" a été convertie en nombre 21. Merveilleux.

Que diriez-vous du troisième, la 1 partie de "21" est un nombre pleine largeur. C'est devenu l'âge: 2. C'est complètement différent de 21.

Au fait, même si vous utilisez "2 1" (avec un espace entre eux) ou "2 a" (avec un caractère autre qu'un nombre), ce sera l'âge: 2.

Qu'est-ce qui ne va pas

Supposons que vous essayez de lire une entrée non numérique et d'appliquer la validation suivante.

  validates :age, format: { with: /\A[0-9]+\z/}

Dans ce cas, si l'utilisateur saisit «2 1», «2 a», etc., la validation sera appliquée à 2 ans et elle sera sauvegardée en tant que données correctes.

Solution "\ _before_type_cast"

En guise de solution, vous pouvez ajouter _before_type_cast à age pour valider la valeur entrée par l'utilisateur au lieu de la valeur après la conversion du type. [* Référence "before_type_cast with rails / validates"](https://dora.bk.tsukuba.ac.jp/~takeuchi/?%E3%82%BD%E3%83%95%E3%83%88%E3 % 82% A6% E3% 82% A7% E3% 82% A2% 2Frails% 2Fvalide% E3% 81% A7before_type_cast)

  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true

Applications (conversion de chaînes de caractères, etc.)

En plus de la validation, _before_type_cast est également utile lorsque vous souhaitez convertir des caractères alphanumériques dans une chaîne de caractères de pleine largeur à demi-largeur, ou lorsque vous souhaitez enregistrer après avoir supprimé les espaces.

À titre d'exemple, le code suivant utilise une gemme appelée Moji pour convertir de pleine largeur en demi-largeur.

Cette fois, j'utilise _before_type_cast parce que je veux aligner l'entrée qui est un mélange de pleine largeur et demi-largeur tel que "21" (1 est pleine largeur) de l'utilisateur à demi-largeur avant validation.

Après avoir reçu la chaîne de caractères telle qu'elle est entrée par l'utilisateur avec age_before_type_cast et l'avoir convertie, la chaîne de caractères convertie est mise en age.

  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true

  before_validation do
    string = self.send(:age_before_type_cast)
    string = Moji.normalize_zen_han(string) 
    send(:write_attribute, :age, string)
  end

La nature de _before_type_cast

Différence entre 〇〇 et 〇〇_before_type_cast

Le premier est la différence entre les attributs normaux et les attributs avec _before_type_cast. La valeur ne peut pas être obtenue avec [: 〇〇_before_type_cast]. En particulier

 irb (main): 001: 0> user = User.new (nom: "Taro", âge: "21")

irb(main):002:0> user.age
=> 21
irb(main):003:0> user.age_before_typecast
=> "21"

irb(main):004:0> user[:age] 
=> 21
irb(main):005:0> user[:age_before_typecast] 
=> nil 

Vous pouvez obtenir la valeur avec user.age_before_type_cast, Quand j'essaye d'obtenir la valeur en utilisant l'utilisateur [: age_before_type_cast], nil est retourné.

〇〇_before_type_cast ne peut pas être mis à jour

irb(main):001:0> user.age = 21
=> 21
irb(main):002:0> user.age_before_type_cast = 21
 => #NoMethodError se produit.

Puisque _before_type_cast sert à vérifier l'entrée avant la conversion de type en age, il n'est bien sûr pas possible de se mettre à jour.

Que se passe-t-il après l'enregistrement

De plus, si vous enregistrez, 〇〇 et 〇〇_before_type_cast seront les mêmes. En particulier,

 irb (main): 001: 0> user = User.new (nom: "Taro", âge: "21")
irb(main):002:0> user.save
irb(main):003:0> user.age_before_typecast
=> 21

Le nombre 21 est renvoyé à la place de la chaîne "21".

Soyez prudent lors de la mise en œuvre ou du test en raison des propriétés ci-dessus!

Article de référence

[before_type_cast with rails / validates](https://dora.bk.tsukuba.ac.jp/~takeuchi/?%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6 % E3% 82% A7% E3% 82% A2% 2Frails% 2Fvalide% E3% 81% A7before_type_cast)

Recommended Posts

[Rails / ActiveRecord] Je souhaite valider la valeur avant la conversion du type (_before_type_cast)
Je veux obtenir la valeur en Ruby
[Rails] Je veux tout réinitialiser car les données de l'environnement local sont incorrectes! Que faire avant ça
Je souhaite créer un formulaire pour sélectionner la catégorie [Rails]
J'ai essayé de comprendre comment la méthode des rails "redirect_to" est définie
J'ai essayé de comprendre comment la méthode des rails "link_to" est définie
Je veux changer la valeur de l'attribut dans Selenium of Ruby
[Ruby] Je souhaite extraire uniquement la valeur du hachage et uniquement la clé
Je veux introduire un comité avec des rails sans devenir trop sale
Je veux obtenir uniquement l'heure à partir des données de type Time ...! [Strftime] * Notes supplémentaires
[Rails] J'ai essayé de faire passer la version de Rails de 5.0 à 5.2
J'ai essayé d'organiser la session en Rails
Je veux var_dump le contenu de l'intention
Code utilisé pour connecter Rails 3 à PostgreSQL 10
Je veux jouer avec Firestore de Rails
Je veux tronquer après la virgule décimale
[Rails] Je veux charger du CSS avec webpacker
[Rails] Je souhaite afficher la destination du lien de link_to dans un onglet séparé
Je veux accéder à l'API avec Rails sur plusieurs docker-composes configurés localement
[Java] Je souhaite calculer la différence par rapport à la date
Je veux intégrer n'importe quel TraceId dans le journal
Je veux juger la gamme en utilisant le diplôme mensuel
Je veux utiliser une petite icône dans Rails
Je veux connaître la réponse de l'application Janken
[Rails] Je ne sais pas comment utiliser le modèle ...
Je souhaite afficher le nom de l'affiche du commentaire
Je souhaite utiliser le mode sombre avec l'application SWT
Je souhaite authentifier les utilisateurs auprès de Rails avec Devise + OmniAuth
Je souhaite définir une fonction dans la console Rails
Je veux appeler la méthode principale en utilisant la réflexion
[Commentaire approximatif] Je veux épouser la méthode du pluck
Je veux retourner la position de défilement de UITableView!
Je souhaite simplifier la sortie du journal sur Android
Je souhaite créer une annotation générique pour un type
Je souhaite ajouter une fonction de suppression à la fonction de commentaire
[Rails] Validez l'heure de début (type datetime) et l'heure de fin
Je veux savoir quelle version de java le fichier jar que j'ai est disponible
Après avoir publié un article avec Rails Simple Calendar, je souhaite le refléter dans le calendrier.
Comment résoudre le problème lorsque la valeur n'est pas envoyée lorsque le formulaire est désactivé dans les rails et envoyé
Je veux renvoyer un type différent de l'élément d'entrée avec Java8 StreamAPI Reduce ()
Je veux savoir si la chaîne de caractères spécifiée est prise en charge par le code de caractère cible
[Débutant] Je souhaite modifier le fichier de migration-Comment utiliser la restauration-
(´-`) .. oO (Je veux trouver facilement la sortie standard" Hello ".
Je veux pousser une application créée avec Rails 6 vers GitHub
Je veux amener Tomcat sur le serveur et démarrer l'application
Je souhaite créer un modèle spécifique d'ActiveRecord ReadOnly
Je souhaite modifier le paramètre de sortie du journal de UtilLoggingJdbcLogger
Je veux appeler une méthode et compter le nombre
Je souhaite utiliser l'API Java 8 DateTime lentement (maintenant)
Je souhaite utiliser la méthode de désinfection autre que View.
Je veux mettre le JDK sur mon PC Mac
Je veux donner un nom de classe à l'attribut select