アプリケーションを開発していると、電話番号や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
を解説したいと思う。