Validators
メール、URL、IPアドレスなど包括的なバリデーター関数のコレクション。ActiveModelと統合可能な実用的なバリデーションライブラリ
Validators
Validatorsは、Rubyアプリケーションで一般的に使用されるバリデーション機能を提供する包括的なライブラリです。メール、URL、IPアドレス、クレジットカード番号など、様々な形式のデータに対する実用的なバリデーターを提供し、ActiveModelと簡単に統合できます。
特徴
- 包括的なバリデーター: メール、URL、IP、クレジットカードなど多様な形式に対応
- ActiveModel統合: Railsモデルでシームレスに使用可能
- カスタマイズ可能: 各バリデーターの設定をカスタマイズ可能
- パフォーマンス重視: 効率的な正規表現とアルゴリズムを使用
- 国際化対応: 多言語でのエラーメッセージサポート
- 軽量: 必要最小限の依存関係
- 実用性重視: 実際のWebアプリケーションで必要な機能に特化
インストール
Gemfileに追加:
gem 'validators'
またはgemで直接インストール:
gem install validators
基本的な使い方
単体でのバリデーション
require 'validators'
# メールアドレスの検証
Validators::EmailValidator.valid?('[email protected]') # => true
Validators::EmailValidator.valid?('invalid-email') # => false
# URLの検証
Validators::UrlValidator.valid?('https://example.com') # => true
Validators::UrlValidator.valid?('not-a-url') # => false
# IPアドレスの検証
Validators::IpValidator.valid?('192.168.1.1') # => true
Validators::IpValidator.valid?('999.999.999.999') # => false
ActiveModelでの使用
class User < ActiveRecord::Base
validates :email, presence: true, email: true
validates :website, url: true, allow_blank: true
validates :ip_address, ip: true, allow_blank: true
validates :credit_card, credit_card: true, allow_blank: true
end
user = User.new(email: 'invalid-email')
user.valid? # => false
user.errors.full_messages
# => ["Email is not a valid email"]
利用可能なバリデーター
メールバリデーター
class User < ActiveRecord::Base
# 基本的なメールバリデーション
validates :email, email: true
# MXレコードチェック付き(追加設定が必要)
validates :business_email, email: { mx: true }
# 使い捨てメールアドレスの禁止
validates :primary_email, email: { disposable: false }
end
# 単体使用
Validators::EmailValidator.valid?('[email protected]') # => true
Validators::EmailValidator.valid?('[email protected]') # => true
Validators::EmailValidator.valid?('invalid.email') # => false
URLバリデーター
class Website < ActiveRecord::Base
# 基本的なURLバリデーション
validates :url, url: true
# HTTPSのみ許可
validates :secure_url, url: { scheme: 'https' }
# 特定のドメインのみ許可
validates :api_endpoint, url: { host: ['api.example.com', 'staging.example.com'] }
end
# 単体使用
Validators::UrlValidator.valid?('https://example.com') # => true
Validators::UrlValidator.valid?('http://test.co.jp/path') # => true
Validators::UrlValidator.valid?('ftp://files.example.com') # => true
Validators::UrlValidator.valid?('not-a-url') # => false
# スキーム指定
Validators::UrlValidator.valid?('https://example.com', scheme: 'https') # => true
Validators::UrlValidator.valid?('http://example.com', scheme: 'https') # => false
IPアドレスバリデーター
class ServerConfig < ActiveRecord::Base
# IPv4とIPv6の両方を許可
validates :ip_address, ip: true
# IPv4のみ
validates :ipv4_address, ip: { format: :v4 }
# IPv6のみ
validates :ipv6_address, ip: { format: :v6 }
# プライベートIPアドレスを禁止
validates :public_ip, ip: { public: true }
end
# 単体使用
# IPv4
Validators::IpValidator.valid?('192.168.1.1') # => true
Validators::IpValidator.valid?('10.0.0.1') # => true
Validators::IpValidator.valid?('255.255.255.255') # => true
# IPv6
Validators::IpValidator.valid?('2001:db8::1') # => true
Validators::IpValidator.valid?('::1') # => true
# 無効なIP
Validators::IpValidator.valid?('999.999.999.999') # => false
Validators::IpValidator.valid?('not-an-ip') # => false
# フォーマット指定
Validators::IpValidator.valid?('192.168.1.1', format: :v4) # => true
Validators::IpValidator.valid?('2001:db8::1', format: :v4) # => false
クレジットカードバリデーター
class Payment < ActiveRecord::Base
# 基本的なクレジットカードバリデーション(Luhnアルゴリズム)
validates :card_number, credit_card: true
# 特定のカードタイプのみ許可
validates :visa_card, credit_card: { type: :visa }
validates :amex_card, credit_card: { type: :american_express }
end
# 単体使用
# 有効なテスト用カード番号
Validators::CreditCardValidator.valid?('4111111111111111') # Visa
Validators::CreditCardValidator.valid?('5555555555554444') # MasterCard
Validators::CreditCardValidator.valid?('378282246310005') # American Express
# 無効なカード番号
Validators::CreditCardValidator.valid?('1234567890123456') # => false
# カードタイプ指定
Validators::CreditCardValidator.valid?('4111111111111111', type: :visa) # => true
Validators::CreditCardValidator.valid?('4111111111111111', type: :mastercard) # => false
電話番号バリデーター
class Contact < ActiveRecord::Base
# 基本的な電話番号バリデーション
validates :phone, phone: true
# 国際形式必須
validates :international_phone, phone: { international: true }
# 特定の国のみ
validates :us_phone, phone: { country: 'US' }
validates :jp_phone, phone: { country: 'JP' }
end
# 単体使用
Validators::PhoneValidator.valid?('090-1234-5678') # => true (日本)
Validators::PhoneValidator.valid?('+81-90-1234-5678') # => true (国際形式)
Validators::PhoneValidator.valid?('(555) 123-4567') # => true (米国)
Validators::PhoneValidator.valid?('invalid-phone') # => false
# 国指定
Validators::PhoneValidator.valid?('090-1234-5678', country: 'JP') # => true
Validators::PhoneValidator.valid?('090-1234-5678', country: 'US') # => false
ActiveModelでの詳細設定
カスタムエラーメッセージ
class User < ActiveRecord::Base
validates :email, email: {
message: 'は有効なメールアドレスを入力してください'
}
validates :website, url: {
message: 'は有効なURLを入力してください'
}
validates :ip_address, ip: {
message: 'は有効なIPアドレスを入力してください'
}
end
条件付きバリデーション
class ApiConfig < ActiveRecord::Base
validates :webhook_url, url: true, if: :webhook_enabled?
validates :fallback_url, url: true, unless: :primary_url_valid?
validates :server_ip, ip: { format: :v4 }, if: -> { environment == 'production' }
private
def webhook_enabled?
webhook_enabled == true
end
def primary_url_valid?
Validators::UrlValidator.valid?(primary_url)
end
end
複数条件でのバリデーション
class UserProfile < ActiveRecord::Base
# 複数のバリデーターを組み合わせ
validates :contact_email, presence: true, email: true, uniqueness: true
validates :personal_website, url: { scheme: ['http', 'https'] }, allow_blank: true
validates :business_phone, phone: { country: 'US' },
if: -> { country_code == 'US' }
validates :backup_server_ip, ip: { format: :v4, public: true },
allow_blank: true
end
カスタムバリデーターの作成
独自バリデーターの定義
class CustomEmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A[^@\s]+@[^@\s]+\z/
record.errors[attribute] << (options[:message] || 'は有効なメールアドレスではありません')
end
# 企業ドメインのみ許可
if options[:corporate_only] && value =~ /@(gmail|yahoo|hotmail)\.com$/
record.errors[attribute] << '個人用メールアドレスは使用できません'
end
end
end
class Employee < ActiveRecord::Base
validates :email, custom_email: { corporate_only: true }
end
複雑なバリデーションロジック
class SecurityValidator < ActiveModel::Validator
def validate(record)
# IPアドレスとポートの組み合わせチェック
if record.ip_address.present? && record.port.present?
unless valid_ip_port_combination?(record.ip_address, record.port)
record.errors.base << 'IPアドレスとポートの組み合わせが無効です'
end
end
# URLとAPIキーの関連性チェック
if record.api_url.present? && record.api_key.present?
unless url_supports_api_key?(record.api_url, record.api_key)
record.errors.base << 'APIキーが指定されたURLに対応していません'
end
end
end
private
def valid_ip_port_combination?(ip, port)
return false if port < 1 || port > 65535
# プライベートIPアドレスでは特定ポートを禁止
if private_ip?(ip) && [22, 23, 3389].include?(port)
return false
end
true
end
def private_ip?(ip)
private_ranges = [
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./
]
private_ranges.any? { |range| ip =~ range }
end
def url_supports_api_key?(url, api_key)
# URLとAPIキーの形式をチェック
return false unless Validators::UrlValidator.valid?(url)
return false unless api_key.length >= 32
# 特定のドメインパターンをチェック
uri = URI.parse(url)
case uri.host
when /api\.github\.com/
api_key =~ /^ghp_/
when /api\.stripe\.com/
api_key =~ /^sk_/
else
true
end
end
end
class ApiIntegration < ActiveRecord::Base
validates_with SecurityValidator
end
エラーハンドリング
詳細なエラー情報の取得
class User < ActiveRecord::Base
validates :email, email: true
validates :website, url: true
validates :phone, phone: true
end
user = User.new(
email: 'invalid-email',
website: 'not-a-url',
phone: 'invalid-phone'
)
if user.valid?
puts "ユーザー情報は有効です"
else
puts "バリデーションエラー:"
user.errors.full_messages.each do |message|
puts "- #{message}"
end
# 特定フィールドのエラーを取得
puts "メールエラー: #{user.errors[:email].join(', ')}"
puts "URLエラー: #{user.errors[:website].join(', ')}"
puts "電話番号エラー: #{user.errors[:phone].join(', ')}"
end
バリデーション結果の詳細分析
def analyze_validation_result(record)
result = {
valid: record.valid?,
errors: {},
warnings: []
}
record.errors.each do |attribute, message|
result[:errors][attribute] ||= []
result[:errors][attribute] << message
# 特定エラーに対する警告
case attribute
when :email
if message.include?('invalid')
result[:warnings] << "メールアドレスの形式を確認してください"
end
when :url
if message.include?('invalid')
result[:warnings] << "URLはhttp://またはhttps://で始まる必要があります"
end
end
end
result
end
# 使用例
user = User.new(email: 'test', website: 'example')
analysis = analyze_validation_result(user)
puts "バリデーション結果: #{analysis[:valid] ? '成功' : '失敗'}"
puts "エラー: #{analysis[:errors]}"
puts "警告: #{analysis[:warnings]}"
Railsでの実用例
ユーザー登録フォーム
class User < ApplicationRecord
validates :email, presence: true, email: true, uniqueness: true
validates :phone, phone: true, allow_blank: true
validates :website, url: true, allow_blank: true
before_validation :normalize_phone
before_validation :normalize_website
private
def normalize_phone
return unless phone.present?
# 電話番号の正規化
self.phone = phone.gsub(/[^\d+\-()]/, '')
end
def normalize_website
return unless website.present?
# URLの正規化
unless website.match?(/^https?:\/\//)
self.website = "https://#{website}"
end
end
end
# コントローラーでの使用
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'ユーザーが正常に作成されました'
else
render :new, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :phone, :website)
end
end
API設定管理
class ApiEndpoint < ApplicationRecord
validates :name, presence: true
validates :url, presence: true, url: { scheme: ['http', 'https'] }
validates :webhook_url, url: true, allow_blank: true
validates :ip_whitelist, ip: { format: :v4 }, allow_blank: true
validate :check_url_accessibility
validate :validate_ip_list
private
def check_url_accessibility
return unless url.present? && Validators::UrlValidator.valid?(url)
begin
uri = URI.parse(url)
# 本番環境でのみアクセシビリティをチェック
if Rails.env.production?
Net::HTTP.get_response(uri)
end
rescue => e
errors.add(:url, "アクセスできないURLです: #{e.message}")
end
end
def validate_ip_list
return unless ip_whitelist.present?
ips = ip_whitelist.split(',').map(&:strip)
ips.each do |ip|
unless Validators::IpValidator.valid?(ip)
errors.add(:ip_whitelist, "無効なIPアドレスが含まれています: #{ip}")
break
end
end
end
end
フォームヘルパーとの統合
<!-- app/views/users/_form.html.erb -->
<%= form_with(model: user, local: true) do |form| %>
<% if user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user.errors.count, "個") %>のエラーがあります:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :email, "メールアドレス" %>
<%= form.email_field :email, class: "form-control" %>
<% if user.errors[:email].any? %>
<div class="error-message">
<%= user.errors[:email].first %>
</div>
<% end %>
</div>
<div class="field">
<%= form.label :website, "ウェブサイト" %>
<%= form.url_field :website, placeholder: "https://example.com", class: "form-control" %>
<% if user.errors[:website].any? %>
<div class="error-message">
<%= user.errors[:website].first %>
</div>
<% end %>
</div>
<div class="field">
<%= form.label :phone, "電話番号" %>
<%= form.telephone_field :phone, placeholder: "090-1234-5678", class: "form-control" %>
<% if user.errors[:phone].any? %>
<div class="error-message">
<%= user.errors[:phone].first %>
</div>
<% end %>
</div>
<div class="actions">
<%= form.submit "保存", class: "btn btn-primary" %>
</div>
<% end %>
テスト例
RSpecでのテスト
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'バリデーション' do
subject { User.new(email: email, website: website, phone: phone) }
let(:email) { '[email protected]' }
let(:website) { 'https://example.com' }
let(:phone) { '090-1234-5678' }
context '有効な値が設定されている場合' do
it { is_expected.to be_valid }
end
describe 'メールアドレスバリデーション' do
context '有効なメールアドレスの場合' do
let(:email) { '[email protected]' }
it { is_expected.to be_valid }
end
context '無効なメールアドレスの場合' do
let(:email) { 'invalid-email' }
it { is_expected.not_to be_valid }
it { expect(subject.tap(&:valid?).errors[:email]).to include('は有効なメールアドレスではありません') }
end
context 'メールアドレスが空の場合' do
let(:email) { '' }
it { is_expected.not_to be_valid }
end
end
describe 'URLバリデーション' do
context '有効なURLの場合' do
let(:website) { 'https://www.example.com/path' }
it { is_expected.to be_valid }
end
context '無効なURLの場合' do
let(:website) { 'not-a-url' }
it { is_expected.not_to be_valid }
end
context 'URLが空の場合' do
let(:website) { '' }
it { is_expected.to be_valid } # allow_blank: true
end
end
describe '電話番号バリデーション' do
context '有効な電話番号の場合' do
let(:phone) { '+81-90-1234-5678' }
it { is_expected.to be_valid }
end
context '無効な電話番号の場合' do
let(:phone) { 'invalid-phone' }
it { is_expected.not_to be_valid }
end
end
end
describe 'Validators単体テスト' do
describe 'EmailValidator' do
it '有効なメールアドレスを正しく判定する' do
expect(Validators::EmailValidator.valid?('[email protected]')).to be true
expect(Validators::EmailValidator.valid?('[email protected]')).to be true
expect(Validators::EmailValidator.valid?('invalid-email')).to be false
end
end
describe 'UrlValidator' do
it '有効なURLを正しく判定する' do
expect(Validators::UrlValidator.valid?('https://example.com')).to be true
expect(Validators::UrlValidator.valid?('http://test.local:3000/path')).to be true
expect(Validators::UrlValidator.valid?('not-a-url')).to be false
end
end
describe 'IpValidator' do
it '有効なIPアドレスを正しく判定する' do
expect(Validators::IpValidator.valid?('192.168.1.1')).to be true
expect(Validators::IpValidator.valid?('2001:db8::1')).to be true
expect(Validators::IpValidator.valid?('999.999.999.999')).to be false
end
end
end
end
ファクトリー設定
# spec/factories/users.rb
FactoryBot.define do
factory :user do
email { Faker::Internet.email }
website { Faker::Internet.url }
phone { '+81-90-1234-5678' }
trait :with_invalid_email do
email { 'invalid-email' }
end
trait :with_invalid_website do
website { 'not-a-url' }
end
trait :with_invalid_phone do
phone { 'invalid-phone' }
end
end
end
# 使用例
describe 'バリデーションテスト' do
it '無効なメールアドレスではバリデーションエラーになる' do
user = build(:user, :with_invalid_email)
expect(user).not_to be_valid
expect(user.errors[:email]).to be_present
end
end
国際化(i18n)
設定ファイル
# config/locales/ja.yml
ja:
activerecord:
errors:
models:
user:
attributes:
email:
email: "は有効なメールアドレスではありません"
website:
url: "は有効なURLではありません"
phone:
phone: "は有効な電話番号ではありません"
ip_address:
ip: "は有効なIPアドレスではありません"
validators:
email:
invalid: "は有効なメールアドレスではありません"
mx_invalid: "のメールサーバーが見つかりません"
disposable: "では使い捨てメールアドレスは使用できません"
url:
invalid: "は有効なURLではありません"
scheme_invalid: "は%{scheme}で始まる必要があります"
ip:
invalid: "は有効なIPアドレスではありません"
v4_invalid: "は有効なIPv4アドレスではありません"
v6_invalid: "は有効なIPv6アドレスではありません"
private_not_allowed: "ではプライベートIPアドレスは使用できません"
phone:
invalid: "は有効な電話番号ではありません"
country_invalid: "は%{country}の電話番号形式ではありません"
credit_card:
invalid: "は有効なクレジットカード番号ではありません"
type_invalid: "は%{type}カードの番号ではありません"
パフォーマンス最適化
バリデーションのキャッシュ
class ValidatorCache
class << self
def email_valid?(email)
Rails.cache.fetch("email_valid:#{email}", expires_in: 1.hour) do
Validators::EmailValidator.valid?(email)
end
end
def url_accessible?(url)
Rails.cache.fetch("url_accessible:#{url}", expires_in: 5.minutes) do
begin
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
response.code.to_i < 400
rescue
false
end
end
end
end
end
class User < ApplicationRecord
validate :check_email_with_cache
private
def check_email_with_cache
return unless email.present?
unless ValidatorCache.email_valid?(email)
errors.add(:email, 'は有効なメールアドレスではありません')
end
end
end
バックグラウンドでのバリデーション
class ExpensiveValidationJob < ApplicationJob
queue_as :default
def perform(record_class, record_id, attribute, value)
record = record_class.constantize.find(record_id)
case attribute
when 'email'
# MXレコードチェック
if mx_record_exists?(value)
record.update_column(:email_verified, true)
else
record.update_column(:email_verified, false)
AdminMailer.invalid_email_notification(record).deliver_now
end
when 'url'
# URLアクセシビリティチェック
if url_accessible?(value)
record.update_column(:url_verified, true)
else
record.update_column(:url_verified, false)
end
end
end
private
def mx_record_exists?(email)
domain = email.split('@').last
Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX).any?
rescue
false
end
def url_accessible?(url)
uri = URI.parse(url)
Net::HTTP.get_response(uri).code.to_i < 400
rescue
false
end
end
# モデルでの使用
class User < ApplicationRecord
validates :email, email: true
validates :website, url: true
after_create :schedule_expensive_validations
private
def schedule_expensive_validations
ExpensiveValidationJob.perform_later(self.class.name, id, 'email', email) if email.present?
ExpensiveValidationJob.perform_later(self.class.name, id, 'url', website) if website.present?
end
end
まとめ
Validatorsライブラリは、Rubyアプリケーションにおいて実用的で包括的なバリデーション機能を提供します。主な利点:
- 実用性: 実際のWebアプリケーションで必要な形式のバリデーションを網羅
- ActiveModel統合: Railsアプリケーションでの使用が簡単
- パフォーマンス: 効率的なアルゴリズムと最適化されたパターンマッチング
- カスタマイズ性: 各バリデーターの詳細設定が可能
- 拡張性: 独自のバリデーターを簡単に追加可能
- 国際化対応: 多言語でのエラーメッセージサポート
特に、メール、URL、IP、電話番号、クレジットカードなどの一般的な形式のバリデーションが必要なWebアプリケーションにおいて、開発効率と品質の向上に大きく貢献するライブラリです。