【Rails入門】ActiveRecord::Validation::valid?で検証しよう

今回は、すでに侍エンジニアブログの別の記事でも書かれているバリデーション(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文字
※コンテキストを指定したパターンです。
emailメールアドレスらしい文字列(正規表現[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?の使いかたを紹介しました。

問題が発生したときにエラーメッセージを取得する方法や、コンテキストを指定してバリデーションの内容を切り替える方法も理解できたでしょうか。

バリデーションは、緩すぎても、厳しすぎても使い勝手が悪くなってしまいますので、色々なパターンを試してみて、ユーザによってちょうど良い設定を探してみてくださいね!

それでは!

この記事を書いた人

侍エンジニア塾は「人生を変えるプログラミング学習」をコンセンプトに、過去多くのフリーランスエンジニアを輩出したプログラミングスクールです。侍テック編集部では技術系コンテンツを中心に有用な情報を発信していきます。

目次