今回は、すでに侍エンジニアブログの別の記事でも書かれているバリデーション(Validation)について、追加情報を仕入れましたのでお伝えします。
Ruby on Rails(以降、Rails)には、ユーザがフォームで入力した値が、開発者が決定したルールに沿っていることを検証するためにバリデーション(Validation)機能が用意されています。
この記事では、(バリデーションを行わずに)オブジェクトを作成した後、任意のタイミングでバリデーションを行う方法を紹介します。
また、応用編としてコンテキストと呼ばれるモノを指定し、状況に応じてバリデーションの内容を切り替える方法も紹介しています。
バリデーションとは
オブジェクトのデータに不適切な内容が設定されていないか検証する機能を、バリデーションと呼びます。
以下のようなバリデーションがよく行われます。
- 電話番号欄に、数字以外の文字を入力していないか
- メールアドレス欄に、メールアドレスとして不適切な文字列が設定されていないか
- 郵便番号欄に、数字3桁+ハイフン(-)+数字4桁以外の文字を入力していないか
ユーザがフォームに入力した内容を検証する方法は、以下の記事で紹介されていますので、あわせてご覧ください。
バリデーションによって、誤ったデータを入力したことをユーザに気付かせ、正しいデータを再入力する仕組みを簡単に実装できます。
ActiveRecord::Validation::valid?とは
この記事で扱うActiveRecord::Validation::valid?は、任意のタイミングでオブジェクトのバリデーション(検証)を行うメソッドです。
たとえば、(ユーザがフォームで入力するのではなく)プログラムでオブジェクトを作成する場合に、ActiveRecord::Validation::valid?メソッドを使ってバリデーションを行います。
あくまでイメージですが、以下のような流れになります。
member = Member.new()
上記のように適当なオブジェクトを作成した後、以下のようにバリデーションを行います。
member.valid?
仮に問題があった場合は、member.errors.messagesでエラーメッセージを確認できます。
動作を理解するためのWebアプリを作成する
ActiveRecord::Validation::valid?メソッドのイメージがわかったところで、動作を確認するために、Rails 5.1をインストールしてWebアプリを作りましょう。
(1)Railsをインストールします。
私は、以下の記事を参考に、VirtualBoxで作成した仮想パソコンにインストールしたLinux Mintに、Railsの開発環境を作成しました。
基本的には記事の手順に従って操作しますが、app/samurai/sample1ディレクトリを作成する代わりに、app/samurai/valid-demoディレクトリを作成しました。
Railsを起動して、ブラウザで画面が表示されることを確認したら、いったんRailsを終了してから次に進みます。
Linux Mintのインストールについては、以下の記事で詳しく説明しています。
(2)新しい「端末」を起動して、以下のコマンドを1行ずつ順番に入力します。
cd app/samurai/valid-demo bin/rails generate scaffold member name:string mail_magazine:boolean email:string post_code:string tel:string bin/rails db:migrate exit
(3)app/models/member.rbを編集します。
変更前:
class Member < ApplicationRecord end
変更後:
# 数字3桁+ハイフン(-)+数字4桁の形式であることを検証するクラス class PostCodeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.present? record.errors.add(attribute, 'は、数字3桁+ハイフン(-)+数字4桁の形式で入力してください。value = ' + value) unless value =~ /A[0-9]{3}-[0-9]{4}z/ end end end class Member < ApplicationRecord before_validation :before_validations # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 }, on: :create # 更新時は、1文字から20文字まで validates :name, length: { minimum: 1, maximum: 20 }, on: :update # メールアドレスは、@の前が1文字以上の文字(次の文字のみ使用可能 a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-) # @の後ろが、英数字で始まり、0〜61文字の英数字または-(ハイフン)が続く。 # @の後ろ2文字目以降に.(ピリオド)がある場合は、.(ピリオド)の後は、英数字で始まり、0〜61文字の英数字または-(ハイフン)が続く。 # mail_magazineをチェックしたときは、emailは必須 validates :email, format: { with: /A[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*Z/}, presence: { if: -> {mail_magazine.present?} } # post_codeは、数字3桁+ハイフン(-)+数字4桁の形式 validates :post_code, post_code: true # telは数字のみ許可。ハイフンやカッコは認めない validates :tel, numericality: { only_integer: true, allow_blank: true} private def before_validations # post_codeとtelの文字列から先頭と末尾のスペースを除去する unless post_code.nil? post_code.strip! end unless tel.nil? tel.strip! end end end
これで準備完了です。
このコードでバリデーション(検証)される内容は、以下のとおりです。
カラム | バリデーション内容 |
---|---|
name | 作成時(on: :create)は、1~10文字 更新時(on: :update)は、1~20文字 ※コンテキストを指定したパターンです。 |
メールアドレスらしい文字列(正規表現[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*) mail_magazineがtrueの場合は、必須 |
|
post_code | 数字3桁+ハイフン(-)+数字4桁(正規表現[0-9]{3}-[0-9]{4})に従う文字列 ※独自のバリデーションメソッドを作成するパターンです。 |
tel | 整数のみ 空欄を認める |
その他 | post_codeとtelは、検証前に先頭と末尾のスペースを除去する |
なお、emailの「メールアドレスらしい文字列(正規表現)」は、「HTML 5.2 W3C Recommendation, 14 December 2017」を利用しています。
詳しくは、以下のページをご覧ください。
参考:https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
ActiveRecord::Validation::valid?を使ってみよう
では、Railsコンソールで、ActiveRecord::Validation::valid?を使ってみましょう。
ActiveRecord::newでバリデーションせずにオブジェクトを作成する
ActiveRecord::createを使うと、バリデーションが行われ、ActiveRecord::Validation::valid?の出番がなくなります。
そこで、ActiveRecord::newを使い、バリデーションを省略してオブジェクトを作成しましょう。
(1)新しい「端末」で以下のコマンドを1行ずつ順番に入力します。
cd app/samurai/valid-demo bin/rails console
(2)以下のコードを入力します。
member = Member.new()
実行結果:
=> #<Member id: nil, name: nil, mail_magazine: nil, email: nil, post_code: nil, tel: nil, created_at: nil, updated_at: nil>
app/models/member.rbを編集したときに、作成時(on: :create)はnameの文字数が1文字から10文字まで(つまり省略できない)に制限していました。
それでも、何ごともなかったかのようにMemberが作成されました。
ActiveRecord::Validation::valid?でバリデーションする
続けて、バリデーションを実行してみましょう。
(1)以下のコードを入力します。
member.valid?
実行結果:
=> false
false(失敗)と表示されました。
バリデーションの結果、何かしら問題が発生しているようですが、詳細は分かりません。
エラーメッセージを確認してみましょう。
(2)以下のコードを入力します。
member.errors.messages
実行結果:
=> {:name=>["is too short (minimum is 1 character)"]}
nameが短すぎる(最低1文字)ことがわかりました。
次は、複数のバリデーションエラーを発生させてみましょう。
(3)以下のコードを1行ずつ順番に入力します。
member = Member.new(mail_magazine: true, post_code: "123", tel: "電話番号") member.valid?
実行結果:
=> false
何かしら問題が発生しているようですが、詳細は分かりませんので、エラーメッセージを確認しましょう。
(4)以下のコードを入力します。
member.errors.messages
実行結果:
=> {:name=>["is too short (minimum is 1 character)"], :email=>["can't be blank"], :post_code=>["は、数字3桁+ハイフン(-)+数字4桁の形式で入力してください。value = 123"], :tel=>["is not a number"]}
以下の4つのエラーメッセージが確認できます。
- :name=>[“is too short (minimum is 1 character)”],
- :email=>[“can’t be blank”],
- :post_code=>[“は、数字3桁+ハイフン(-)+数字4桁の形式で入力してください。value = 123”],
- :tel=>[“is not a number”]}
エラーメッセージに対応せずに、保存してみましょう。
(5)以下のコードを入力します。
member.save
実行結果:
(0.1ms) begin transaction (0.2ms) rollback transaction => false
保存しようとしますが、保存に失敗し、ロールバックしています。
ここで紹介した例のように、バリデーションに失敗しているオブジェクトは保存できません。
※member.save(validate: false)とすれば、強制的に保存できます。
ActiveRecord::saveでオブジェクトを保存する
あれこれ配慮して、バリデーションエラーが発生しない状態にしてから、ActiveRecord::saveでオブジェクトを保存するのが良いでしょう。
オブジェクトを保存してみます。
(1)以下のコードを入力します。
member = Member.new(name: "山田太朗", mail_magazine: true, email: "yamada.taro@example.jp", post_code: "101-0001", tel: "0312345678") member.valid?
実行結果:
=> true
バリデーションエラーが発生していません。
念のためエラーメッセージも確認してみましょう。
(2)以下のコードを入力します。
member.errors.messages
実行結果:
=> {}
こちらも何もありませんね。
(3)以下のコードを入力します。
member.save
実行結果:
(0.1ms) begin transaction SQL (0.2ms) INSERT INTO "members" ("name", "mail_magazine", "email", "post_code", "tel", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["name", "山田太朗"], ["mail_magazine", "t"], ["email", "yamada.taro@example.jp"], ["post_code", "101-0001"], ["tel", "0312345678"], ["created_at", "2018-07-31 14:40:01.744127"], ["updated_at", "2018-07-31 14:40:01.744127"]] (6.7ms) commit transaction => true
無事に保存できました。
この後の説明の都合、Railsコンソールを終了しておきましょう。
(3)以下のコードを入力します。
exit
コンテキストを指定してバリデーションする
コンテキストと呼ばれるモノを指定すると、状況に応じてバリデーションの内容を切り替えられます。
たとえば、初めてデータベースに登録するときはnameは10文字までに制限されているけれど、更新時は20文字まで許可するといったこともできます。
ただ、これが便利!と思える状況が思いつかないのですが…、できることなので紹介しておきましょう。
app/models/member.rbの以下の部分に注目してください。
(省略) # nameの文字数は、1文字から10文字まで validates :name, length: { minimum: 1, maximum: 10 }, on: :create # 更新時は、1文字から20文字まで validates :name, length: { minimum: 1, maximum: 20 }, on: :update (省略)
これが、コンテキストによってバリデーションの内容を切り替えているコードです。
このコードがどのように動作するのか確認してみましょう。
(1)「端末」で、以下のコマンドを入力します。
bin/rails console
(2)以下のコードを1行ずつ順番に入力します。
member = Member.new(name: "12345678901234567890") member.valid?
実行結果:
=> false
ここでは、nameに20文字の文字列を設定して、新規作成しています。
新規作成時は、「on: :create」が指定された行のバリデーションによって、バリデーションエラーが発生します。
次に、コンテキストを変更し、更新時(「on: :update」が指定された行)のバリデーションで検証してみましょう。
(3)以下のコードを入力します。
member.valid?(context: :update)
実行結果:
=> true
今度は「context: :update」を指定することで、「on: :update」が指定された行のバリデーションによって、バリデーションエラーが発生していません。
まとめ
今回は、任意のタイミングでバリデーションを行うActiveRecord::Validation::valid?の使いかたを紹介しました。
問題が発生したときにエラーメッセージを取得する方法や、コンテキストを指定してバリデーションの内容を切り替える方法も理解できたでしょうか。
バリデーションは、緩すぎても、厳しすぎても使い勝手が悪くなってしまいますので、色々なパターンを試してみて、ユーザによってちょうど良い設定を探してみてくださいね!
それでは!