こんにちは!フリーランスの桃太郎です。
Ruby on Railsには、ユーザがフォームで入力した値が、開発者が決定したルールに沿っていることを検証するためにActiveModel::Validationsモジュール(以降、Validations)が用意されています。
この記事では、
・Validationsとは
・Validationsの基本的な実装方法
という基本的な内容から、
・条件に一致したときだけValidationsを行う方法
・Validationsの前に実行する処理を定義する方法
・Validationsのカスタマイズ方法
・エラーメッセージを日本語にする方法
などの応用的な使いかたを、Validationsについて正しく理解し、必要な場面で使いこなせるように、わかりやすく解説します。
Validationsとは
例えば、ユーザに会員登録をしてもらうために、以下のような入力フォームを作成したとします。
開発者は、Tel欄には電話番号(つまり数字のみ)が入力されることを期待します。
しかし、ユーザが全角数字など数字以外の文字を入力することもあるでしょう。
そのような場合は、そのまま登録完了するのではなく、間違っていることを気付かせ、入力し直してもらう方がユーザにとっても開発者にとっても親切なシステムと言えます。
「Tel欄に数字以外の文字を入力していないか」を検証する機能を、Validationsが提供しています。
サンプルアプリケーションを作成する
検証機能の実装方法をみていくうえで、以下の記事を参考にRuby on Railsの開発環境を構築しましょう。
そして、以下の記事でも紹介されているScaffoldを使って、アプリのひな形を作ります。
具体的には、以下のコマンドを1行ずつ順番に入力します。
[code]
bin/rails generate scaffold member name:string mail_magazine:boolean email:string post_code:string tel:string
bin/rails db:migrate
[/code]
これで、以下のようなデータベースを利用するサンプルアプリケーションが作成されました。
フィールド名 | 説明 |
---|---|
name | 氏名 |
mail_magazine | メールマガジンの購読 |
メールアドレス | |
post_code | 郵便番号 |
tel | 電話番号 |
以降、このサンプルアプリケーションに様々な検証機能を追加します。
【基本】文字数を検証する
「氏名」を入力する欄について、「1文字から10文字まで」というルールを設定します。
※氏名は一般的に10文字で収まりません。実際に制限をつける場合は、255文字までにするなど、制限を緩くしましょう。
models/member.rbを以下のように編集します。
class Member < ApplicationRecord # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 } end
ここで使用しているvalidatesメソッドは、ActiveModel::Validationsモジュールのメソッドです。
validatesメソッドの後に、「name」(氏名)のように検証対象のフィールド名を指定します。
そして、文字数を検証することを指定する「length」と、その最小値(minimum)と最大値(maximum)を設定します。
これで、「1文字から10文字まで」というルールが設定されました。
【応用1】条件付きで検証する
「メールマガジンを受け取るチェックを付けた場合は、メールアドレスを必須にする」というように、条件付きで検証の有無を切り替えることもできます。
まず、mail_magazineのチェックの有無にかかわらず、emailを必須にする場合の記載方法を確認しておきましょう。
models/member.rbを以下のように編集します。
※先ほどの続きのコードのため、「validates :name, …」も記載されています。
class Member < ApplicationRecord # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 } # emailは必須 validates :email, presence: true end
validatesメソッドの後に、「email」(メールアドレス)のように検証対象のフィールド名を指定します。
そして、入力済みかを常に検証することを指定する「presence: true」を指定しました。
ここに「メールマガジンを受け取るチェックをつけた場合は」という条件をつけるには、以下のように変更します。
class Member < ApplicationRecord # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 } # mail_magazineをチェックしたときは、emailは必須 validates :email, presence: { if: '!mail_magazine.blank?' } end
常に検証することを指定する「true」を、mail_magazineにチェックを付けたことを条件とする「if: ‘!mail_magazine.blank?’」に変更しました。
ここはちょっとトリッキーな気がしますので、解説しておきましょう。
「mail_magazine.blank?」ではチェックが付いていないときに真(true)になりますが、「!」をつけるとチェックが付いているときに真(true)になります。
つまり、チェックボックスにチェックが付いているときは「presence: true」、チェックが付いていないときは「presence: false」となり、「メールマガジンを受け取るチェックを付けた場合は、メールアドレスを必須にする」が検証できるというワケです。
【応用2】カスタムバリデーションを行う
ここまでに紹介した「length」や「presence」は、ActiveModel::Validationsモジュールで標準で用意されている検証機能でした。
今度は、複雑な検証機能を作成する方法、格好よく言うと、検証機能をカスタマイズする方法を説明します。
具体的には、郵便番号が数字3桁+ハイフン(-)+数字4桁の形式であることを検証する機能を作成します。
models/member.rbを以下のように編集してください。
# 数字3桁+ハイフン(-)+数字4桁の形式であることを検証するクラス class PostCodeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, 'は、数字3桁+ハイフン(-)+数字4桁の形式で入力してください。') unless value =~ /\A[0-9]{3}-[0-9]{4}\z/ end end class Member < ApplicationRecord # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 } # mail_magazineをチェックしたときは、emailは必須 validates :email, presence: { if: '!mail_magazine.blank?' } # post_codeは、数字3桁+ハイフン(-)+数字4桁の形式 validates :post_code, post_code: true end
ActiveModel::EachValidatorを継承した独自の検証クラスを定義しています。
クラス名の付け方と利用方法には関連があります。
上のサンプルのように「PostCodeValidator」という検証クラス名にした場合は、検証機能を利用する際に「length」や「presence」を指定する代わりに、「post_code」を指定できるようになります。
検証クラス名の「PostCode」のうち、先頭の大文字「P」は小文字「p」になり、途中の大文字「C」はアンダースコア+小文字「c」になる決まりです。
そして、PostCodeValidatorクラスのvalidate_eachメソッドには、検証方法を実装します。
このメソッドの引数には、以下の情報が渡されます。
引数 | 渡される情報 |
---|---|
record | 検証対象のモデルオブジェクト(ここではMember) |
attribute | 検証対象の属性名 |
value | 検証対象の属性値 |
そこで、valueが「数字3桁+ハイフン(-)+数字4桁」の形式になっていることを、正規表現(/\A[0-9]{3}-[0-9]{4}\z/)を使用して検証します。
正規表現を使っているときは、\Aや\zを使用することがとても大切です。
以下の記事が参考になりますので、必ずご覧ください。
参考:https://qiita.com/jnchito/items/ea7832df6f64a9034872
【実践】検証する前に処理を実行する
実践的な内容をもうひとつ。
入力値を検証する前にちょっとした処理を実行する方法を紹介しましょう。
具体的には、以下のように郵便番号や電話番号に入力されてしまった不要なスペースを削除する方法です。
ユーザの入力値:(複数のスペース)0312345678(複数のスペース)
以下のサンプルの「before_validation」と「before_validations」に注目してください。
# 数字3桁+ハイフン(-)+数字4桁の形式であることを検証するクラス class PostCodeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, 'は、数字3桁+ハイフン(-)+数字4桁の形式で入力してください。') unless value =~ /\A[0-9]{3}-[0-9]{4}\z/ end end class Member < ApplicationRecord before_validation :before_validations # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 } # mail_magazineをチェックしたときは、emailは必須 validates :email, presence: { if: '!mail_magazine.blank?' } # post_codeは、数字3桁+ハイフン(-)+数字4桁の形式 validates :post_code, post_code: true # telは数字のみ許可。ハイフンやカッコは認めない validates :tel, numericality: { only_integer: true} private def before_validations # post_codeとtelの文字列から先頭と末尾のスペースを除去する post_code.strip! tel.strip! end end
before_validationメソッドでは、すべての検証の前に一度だけ実行されるメソッド(この例ではbefore_validations)を登録できます。
before_validationメソッドにプライベートメソッドを指定し、そのプライベートメソッドには検証前の処理を定義するという2段構えです。
このようにすると、(nameやemailも含めて)すべての検証が実行される前に、post_codeとtelの文字列から先頭と末尾のスペースが除去できます。
エラーメッセージをカスタマイズする
ここまでのコードで、すべての検証に引っかかってしまった場合の画面は、以下のようになります。
よく見ると、エラーメッセージが英語だったり日本語だったりしますね。
これを、すべて日本語にしてみましょう。
(1)以下のコマンドを入力します。
[code]
wget https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -P config/locales/
[/code]
以下のGitHubのrails-i18nリポジトリから、ja.ymlがダウンロードされてconfig/localesに保存されます。
参考:https://github.com/svenfuchs/rails-i18n
(2)「config/application.rb」を以下のように編集します。
[code]
require_relative ‘boot’
require ‘rails/all’
# Require the gems listed in Gemfile, including any gems
# you’ve limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Sample1
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.1
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# — all .rb files in that directory are automatically loaded.
config.i18n.default_locale = :ja
end
end
[/code]
後ろから3行目に「config.i18n.default_local = :ja」を追加しています。
(3)サーバーを再起動します。
これで、すべての検証に引っかかってしまった場合の画面は、以下のように変わります。
残念ながら「4 error prohibited this member from being saved:」は英語のままですね…。
これを手っ取り早く直すなら、「app/views/members/_form.html.erb」を修正します。
このファイルは、Scaffoldによって生成されていますので、根本的にはScaffoldを修正する必要がありそうですね。
まとめ
ここでは、Ruby on Railsに用意されているValidations(検証機能)について、以下の内容を紹介しました。
・検証に条件をつける方法(presence、if)
・カスタムバリデーション(ActiveModel::EachValidatorの継承)
・検証前の処理を定義する方法(before_validation)
・エラーメッセージの日本語化(ja.yml、config.i18n.default_locale)
ユーザが入力する値は基本的には誤っている可能性があることを前提に設計をすることがユーザにとって親切なシステムです。
また、危険な入力をするユーザも存在することを前提に、セキュリティにも十分注意しましょう。
簡単なところですが、正規表現を使って検証しなければいけないなら「\A」と「\z」を利用しましょう。
もし、検証機能の使い方を忘れてしまったら、この記事を思い出してくださいね!