プログラミング的なSomething

プログラミング的なSomething

ITエンジニア(?)目線で生活・自転車・トレーニング話を綴ります

RailsにおけるValidationの処理をまとめてみた

Validationのお作法をまとめてみました

バリデーションの基本的なパターンは存在性、長さ、フォーマット、一意性の確認となります。 整理のため、これら4つの処理をRailsで行う際のお作法をまとめてみます。テストコードも最後に付記しています。

存在性の検証

Userというname、emailを持つクラスを考えてみます。 「presence: true」で存在性を確認することができます。

class User < ActiveRecord::Base 
  attr_accessible :name, :email

  validates :name, presence: true
end

長さの検証

同じくUserクラスで長さの検証を行います。名前が長すぎると困るので50文字に制限した処理にします。 length: { maximum: 50 }を付けることで検証が可能です。

class User < ActiveRecord::Base 
  attr_accessible :name, :email

  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true
end

フォーマットの検証

emailが正しいフォーマットで入力されているかを検証します。 正規表現とformat: { with:定数 }で検証できます。

class User < ActiveRecord::Base
  attr_accessible :name, :email

  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
end

一意性の検証

メールアドレスに一意性を持たせるための処理を書きます。 ただし、一意性のテストには実際のレコードをデータベースに登録して検証する必要があります。 また、登録者が(クリックの連打などで)二重登録をしてしまった場合、容易に一意性が崩れてしまいます。そこで、データベースにemailの索引を持たせて、一意性を検証する必要があります。また、メールアドレスは大文字・小文字を区別しないので、データベースに登録前に小文字に戻すという一手間も必要になります。

大文字・小文字の区別を無視した検証が以下の通りです。uniquenessとcase_sensible: falseを書く必要があります。

class User < ActiveRecord::Base
  .
  .
  .
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

索引を持たせるため、データモデリングの更新が必要です。以下のmigrationを行います。

rails generate migration add_index_to_users_email

作成されたファイルを以下のように書き換えます。 usersテーブルのemailカラムにインデックスを追加しています。

class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end

最後にmigrationを忘れずに

bundle exec rake db:migrate

データベース登録前にemailを全て小文字に変換します。 ActiveRecordのオブジェクトが持続している間に、オブジェクトを呼び出すコールバックの機能を用いて、小文字に変換後のデータをsaveします。 before_saveメソッドで呼び出し、downcaseメソッドで小文字に変換してください。

class User < ActiveRecord::Base 
  attr_accessible :name, :email

  before_save { |user| user.email = email.downcase }
  .
  .
  .
end

テストコード

テストコードは以下のとおりです。

require 'spec_helper'

describe User do
  before { @user = User.new(name:"Example User",email:"user@example.com") }

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should be_valid }
  
  describe "when name is not present" do
    before { @user.name = "" }
    it { should_not be_valid }
  end

  describe "when email is not present" do
    before { @user.email = "" }
    it { should_not be_valid }
  end

  describe "when name is too long" do
    before { @user.name = "a" * 51 }
    it { should_not be_valid }
  end

  describe "when email format is invalid" do
    it "should be invalid" do
    addresses = %w[user@foo,com user_at_foo.org example.user@foo.
                     foo@bar_baz.com foo@bar+baz.com]
      addresses.each do |invalid_address|
        @user.email = invalid_address
        @user.should_not be_valid
      end
    end
  end

  describe "when email format is valid" do
    it "should be valid" do
      addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
      addresses.each do |valid_address|
        @user.email = valid_address
        @user.should be_valid
      end
    end
  end

  describe "when email address is already taken" do
    before do
      user_with_same_email = @user.dup
      user_with_same_email.email = @user.email.upcase
      user_with_same_email.save
    end

    it { should_not be_valid }
  end
end