アプリケーションを開発していると、電話番号やURL、カナなど複数のモデルで同じバリデーションを定義していることがある。 上記の例くらいなら、同じバリデーションルールを複数箇所で書いても良さそうだが、変更が生じた時やもう少し複雑なバリデーションを記述する場合、面倒なことになる。 そこで今回は、Railsではどうやってバリデーションルールを共通化させるかについて解説する。
まず、Railsではこのようなルールを定義して利用するための基底クラスとして、ActiveModel::EachValidator
とActiveModel::Validator
が用意されている。
本記事ではまずActiveModel::EachValidator
についての解説を行い、次回の記事でActiveModel::Validator
の解説をしたいと思う。
ActiveModel::EachValidatorとは
ある1つの属性のバリデーションルールを定義する時に利用する。例えば電話番号、メールアドレス、郵便番号、URL、カナなどのフォーマットである。
使い方
例えば電話番号のバリデーションルールを定義したい時は、下記のように書く。
# app/validators/tel_format_validator.rb
class TelFormatValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# 電話番号のフォーマットかどうかを確認したいので、空文字は許可
return if options[:allow_blank] && value.length.zero?
# 固定電話と携帯番号(ハイフンなし10桁 or 11桁)を許可
unless value =~ /\A\d{10}$|^\d{11}\z/
record.errors[attribute] << (options[:message] || '電話番号の形式に誤りがあります')
end
end
end
ActiveModel::EachValidator
を継承したクラスでは、validate_each
というインスタンスメソッドにバリデーションルールを実装し、その引数であるrecord
、attribute
、value
にはそれぞれ、対象のオブジェクト、対象の属性、値が入る。
options[:message]
と書くことで、オプションとしてmessageパラメータの文字列が送られてきた時に、バリデーションメッセージを自由に設定することも出来る。
上で実装出来たら、モデルのvalidates
メソッドのオプションとして使える。
# app/models/user.rb
class User < ApplicationRecord
validates :tel, presence: true, tel_format: true
validates :tel2, presence: true, tel_format: { message: '電話番号2の形式に誤りがあります' }
end
テストを書く
Vlidatorのテストを書いてみる。実際のユースケースに合わせて、StructクラスにActiveModel::Validations
をincludeする。
spec/validators/tel_format_validator_spec.rb
RSpec.describe TelFormatValidator, type: :validator do
let(:clazz) do
Struct.new(:tel) do
include ActiveModel::Validations
validates :tel, presence: true, tel_format: true
end
end
describe '#validate_each' do
subject { clazz.new(tel) }
context '携帯電話の様式' do
let(:tel) { '09012345678' }
it { is_expected.to be_valid }
end
context '固定電話の様式' do
let(:tel) { '0312345678' }
it { is_expected.to be_valid }
end
end
describe '異常系' do
subject { clazz.new(tel) }
context '9桁の電話番号' do
let(:tel) { '031234567' }
it { is_expected.to be_invalid }
end
context '12桁の電話番号' do
let(:tel) { '090123456789' }
it { is_expected.to be_invalid }
end
end
end
まとめ
1つの属性に対してバリデーションルールを設定できるActiveModel::EachValidator
を解説した。
「じゃあ、2つ以上の属性が絡み合うバリデーションの場合」はどうするの?と疑問に思うと思うので、次回は複数の属性に関するバリデーションルールを設定出来るActiveModel::Validator
を解説したいと思う。