RailsでCustom Validatorの実装例


最初に

みなさん、Custom Validator使ってますか?

rails guideここにも記載されていますがActiveModel::ValidatorActiveModel::EachValidatorを使って自作のvalidatorを作成することができます。

異なるmodelで同じようなvalidationを実行する場合は以下のようなメリットがあるので是非使っていきましょう。
・modelのコードを減らせる
・specの行数を減らせる
(Validator Classへspecを書けば良いので同じようなspecを減らせます)

実装例

よくあるパターンだと思うのですが異なるmodelでメールアドレスカラムを持っており、同じようなvalidationを実装する場合の実装例を記載します。

  • 以下のようなEmailのValidator Classを作成します。
app/validators/email_validator.rb
# frozen_string_literal: true

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)
      record.errors.add(attribute, options[:message] || "の形式が不正です")
    end
  end
end
  • deviseを使っていてdeviseと同じEmailのvalidationを使いたい場合は以下のような書き方もできます。
unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)

unless value.match(Devise.email_regexp)
  • modelでの使い方は以下になります。
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
  • エラーメッセージは以下のような感じになると思います。
メールアドレスの形式が不正です
  • エラーメッセージを変えたい場合もあると思います。
  • その場合は以下のようにメッセージを個別で設定することができます。

class User < ApplicationRecord
    validates :email, allow_nil: true, email: { message: "正しいメールアドレスの形式で入力してください" }
end
  • 次にspecの書き方のサンプルを記載します。
# frozen_string_literal: true

require "rails_helper"

describe EmailValidator do
  let(:model_class) do
    Struct.new(:mail_address) do
      include ActiveModel::Validations

      def self.name
        "DummyModel"
      end

      validates :mail_address, allow_nil: true, email: true
    end
  end

  describe "#validate" do
    subject { model_class.new(email) }

    describe "登録可能な形式" do
      context "nil は登録できる" do
        let(:email) { nil }
        it { is_expected.to be_valid }
      end

      context "「[email protected]」は登録できる" do
        let(:email) { "[email protected]" }
        it { is_expected.to be_valid }
      end
    end

    describe "登録不可能な形式" do
      context "「abc」は登録できない" do
        let(:email) { "abc" }
        it { is_expected.not_to be_valid }
      end

      context "「[email protected]」は登録できない" do
        let(:email) { "[email protected]" }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

最後に

email以外にも画像やURLのvalidationなど使えるところは色々あるのでお試し下さい!