ActiveModel::Validations
GitHub概要
スター57,276
ウォッチ2,311
フォーク21,912
作成日:2008年4月11日
言語:Ruby
ライセンス:MIT License
トピックス
activejobactiverecordframeworkhtmlmvcrailsruby
スター履歴
データ取得日時: 2025/8/13 01:43
ライブラリ
ActiveModel::Validations
概要
ActiveModel::ValidationsはRuby on Railsフレームワークの中核となるバリデーション機能です。Active RecordとActive Modelで使用される包括的なバリデーションシステムで、10年以上にわたってRailsアプリケーションの標準的なバリデーション手法として広く採用されています。豊富な内蔵バリデーターと柔軟なカスタムバリデーション機能により、データの整合性とビジネスルールの実装を簡潔かつ宣言的に記述できます。オブジェクトレベルでのバリデーションとモデル層での責任分離を可能にし、堅牢なWebアプリケーション開発を支援します。
詳細
ActiveModel::Validations 7.1.5は2025年現在の最新版で、Rails 7.1フレームワークの一部として提供されています。Active Recordモデルに限らず、Plain Old Ruby Objects(PORO)でも使用可能な柔軟な設計により、ドメインモデルやService Objectでも活用できます。内蔵バリデーターには、presence、length、format、uniqueness、numerical、inclusion、exclusionなど網羅的な機能を提供。条件付きバリデーション、カスタムバリデーター、エラーハンドリング、国際化対応など、エンタープライズレベルのアプリケーション開発で必要な全ての機能を兼ね備えています。
主な特徴
- 包括的な内蔵バリデーター: 一般的なバリデーションニーズをカバーする豊富なバリデーター
- 柔軟なカスタムバリデーション: ビジネスロジックに特化したカスタムバリデーターの実装
- 条件付きバリデーション: :if, :unless, :onオプションによる動的バリデーション制御
- 詳細なエラーハンドリング: エラーオブジェクトによる包括的なエラー情報管理
- 国際化(I18n)対応: エラーメッセージの多言語対応とカスタマイズ
- Rails統合: Active RecordとActive Modelでのシームレスな統合
メリット・デメリット
メリット
- Railsエコシステムでの標準的な地位と豊富な実績
- 宣言的で読みやすいバリデーション記述
- 包括的な内蔵バリデーターによる開発効率
- 条件付きバリデーションによる複雑なビジネスルールの実装
- 詳細なエラーハンドリングとi18n対応
- Railsの他の機能(forms、JSON API等)との深い統合
デメリット
- Rails/ActiveModelに依存するため単体での使用が困難
- 複雑なバリデーションロジックでのパフォーマンス課題
- 大量データの一括バリデーション時のメモリ使用量
- Rails以外のRubyプロジェクトでの統合コスト
- カスタムバリデーターの学習コストとテストの複雑さ
- スキーマ変更時のバリデーション同期の手動管理
参考ページ
- Active Record Validations — Ruby on Rails Guides
- ActiveModel::Validations API Documentation
- Rails GitHub Repository
書き方の例
インストールと基本セットアップ
# Gemfile
gem 'rails', '~> 7.1.0'
# または ActiveModel のみを使用する場合
gem 'activemodel', '~> 7.1.0'
# Bundlerでインストール
bundle install
# Rails新規プロジェクト作成
rails new myapp
cd myapp
# Active Model単体での使用例
require 'active_model'
class User
include ActiveModel::Validations
include ActiveModel::AttributeAssignment
attr_accessor :name, :email, :age
def initialize(attributes = {})
assign_attributes(attributes)
end
end
基本的なバリデーション定義
# Active Recordモデルでの基本的なバリデーション
class User < ApplicationRecord
# 必須項目バリデーション
validates :name, presence: true
validates :email, presence: true
# 文字列長バリデーション
validates :name, length: { minimum: 2, maximum: 50 }
validates :password, length: { minimum: 8 }
# 形式バリデーション
validates :email, format: {
with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
message: "有効なメールアドレスを入力してください"
}
# 数値バリデーション
validates :age, numericality: {
greater_than: 0,
less_than: 150,
only_integer: true
}
# 一意性バリデーション
validates :email, uniqueness: { case_sensitive: false }
validates :username, uniqueness: { scope: :organization_id }
# 選択肢バリデーション
validates :status, inclusion: { in: %w[active inactive pending] }
validates :role, exclusion: { in: %w[admin super_admin] }
# 確認フィールドバリデーション
validates :password, confirmation: true
validates :email, confirmation: true
end
# バリデーションの実行例
user = User.new(
name: "田中太郎",
email: "[email protected]",
age: 30,
password: "securepass123",
password_confirmation: "securepass123"
)
# バリデーション実行
if user.valid?
puts "バリデーション成功"
user.save
else
puts "バリデーションエラー:"
user.errors.full_messages.each do |error|
puts "- #{error}"
end
end
# 個別属性のバリデーション
if user.invalid?(:email)
puts "メールアドレスが無効です: #{user.errors[:email].join(', ')}"
end
# Plain Old Ruby Object (PORO) での使用
class Contact
include ActiveModel::Validations
include ActiveModel::AttributeAssignment
attr_accessor :name, :email, :subject, :message
validates :name, :email, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :subject, length: { minimum: 5, maximum: 100 }
validates :message, length: { minimum: 10, maximum: 1000 }
def initialize(attributes = {})
assign_attributes(attributes)
end
def submit
return false unless valid?
# メール送信処理
ContactMailer.new_contact(self).deliver_now
true
end
end
# POROwithバリデーションの使用例
contact = Contact.new(
name: "山田花子",
email: "[email protected]",
subject: "お問い合わせ",
message: "サービスについてご質問があります。"
)
if contact.submit
puts "お問い合わせが送信されました"
else
puts "エラー: #{contact.errors.full_messages.join(', ')}"
end
条件付きバリデーションとカスタムバリデーター
class User < ApplicationRecord
# 条件付きバリデーション - :if, :unless オプション
validates :terms_accepted, acceptance: { message: "利用規約に同意してください" },
if: :requires_terms_acceptance?
validates :tax_id, presence: true, if: :company_user?
validates :age, presence: true, unless: :skip_age_validation?
# プロック条件
validates :password, length: { minimum: 12 },
if: ->(user) { user.role == 'admin' }
# 複数条件
validates :phone_number, presence: true,
if: [:profile_complete?, :notification_enabled?]
# コンテキスト指定バリデーション
validates :terms_accepted, acceptance: true, on: :create
validates :current_password, presence: true, on: :update
validates :admin_approval, presence: true, on: :admin_review
# カスタムバリデーター(インラインメソッド)
validate :password_complexity
validate :email_domain_allowed
validate :unique_username_per_organization
private
def requires_terms_acceptance?
new_record? && role != 'admin'
end
def company_user?
user_type == 'company'
end
def skip_age_validation?
admin? || age_verification_waived?
end
def profile_complete?
[name, email, address].all?(&:present?)
end
def notification_enabled?
notifications.present?
end
# カスタムバリデーションメソッド
def password_complexity
return unless password.present?
errors.add(:password, "大文字を含む必要があります") unless password.match?(/[A-Z]/)
errors.add(:password, "小文字を含む必要があります") unless password.match?(/[a-z]/)
errors.add(:password, "数字を含む必要があります") unless password.match?(/\d/)
errors.add(:password, "特殊文字を含む必要があります") unless password.match?(/[!@#$%^&*(),.?":{}|<>]/)
end
def email_domain_allowed
return unless email.present?
allowed_domains = ['company.com', 'partner.org']
domain = email.split('@').last&.downcase
unless allowed_domains.include?(domain)
errors.add(:email, "許可されたドメインのメールアドレスを使用してください")
end
end
def unique_username_per_organization
return unless username.present? && organization_id.present?
existing_user = User.where(
username: username,
organization_id: organization_id
).where.not(id: id).first
if existing_user
errors.add(:username, "この組織内で既に使用されているユーザー名です")
end
end
end
# カスタムバリデータークラス
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless value.present?
unless URI::MailTo::EMAIL_REGEXP.match?(value)
record.errors.add(attribute, "有効なメールアドレス形式ではありません")
end
# 追加のメールドメイン検証
if options[:allowed_domains]
domain = value.split('@').last&.downcase
unless options[:allowed_domains].include?(domain)
record.errors.add(attribute, "許可されていないドメインです")
end
end
# 使い捨てメールアドレスの検証
if options[:block_disposable]
disposable_domains = ['tempmail.org', '10minutemail.com', 'guerrillamail.com']
domain = value.split('@').last&.downcase
if disposable_domains.include?(domain)
record.errors.add(attribute, "使い捨てメールアドレスは使用できません")
end
end
end
end
class PhoneNumberValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless value.present?
# 日本の電話番号形式をチェック
phone_regex = /\A(\+81|0)[0-9\-\(\)\s]{8,14}\z/
unless phone_regex.match?(value)
record.errors.add(attribute, "有効な電話番号形式ではありません")
end
end
end
# カスタムバリデーターの使用
class Company < ApplicationRecord
validates :contact_email, email: {
allowed_domains: ['company.com', 'business.org'],
block_disposable: true
}
validates :phone, phone_number: true
end
# 複雑なビジネスルールバリデーション
class Order < ApplicationRecord
validates :delivery_date, presence: true
validate :delivery_date_must_be_future
validate :delivery_date_not_on_weekend
validate :sufficient_inventory
validate :payment_method_valid
private
def delivery_date_must_be_future
return unless delivery_date.present?
if delivery_date <= Date.current
errors.add(:delivery_date, "配送日は今日以降の日付を選択してください")
end
end
def delivery_date_not_on_weekend
return unless delivery_date.present?
if delivery_date.saturday? || delivery_date.sunday?
errors.add(:delivery_date, "配送日は平日を選択してください")
end
end
def sufficient_inventory
return unless items.present?
items.each do |item|
if item.quantity > item.product.stock_quantity
errors.add(:items, "#{item.product.name}の在庫が不足しています")
end
end
end
def payment_method_valid
return unless payment_method.present?
case payment_method
when 'credit_card'
errors.add(:payment_method, "クレジットカード情報が無効です") unless valid_credit_card?
when 'bank_transfer'
errors.add(:payment_method, "銀行口座情報が無効です") unless valid_bank_account?
end
end
def valid_credit_card?
# クレジットカード検証ロジック
credit_card_number.present? && credit_card_number.match?(/\A\d{13,19}\z/)
end
def valid_bank_account?
# 銀行口座検証ロジック
bank_account_number.present? && bank_account_number.match?(/\A\d{7,8}\z/)
end
end
エラーハンドリングとメッセージカスタマイズ
# エラーオブジェクトの詳細操作
class Product < ApplicationRecord
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
validates :price, numericality: { greater_than: 0 }
validates :category, inclusion: { in: %w[electronics clothing books food] }
end
product = Product.new(name: "", price: -100, category: "invalid")
# バリデーション実行
unless product.valid?
puts "=== エラー詳細 ==="
# 全てのエラーメッセージ
puts "全エラー: #{product.errors.full_messages}"
# 属性別エラー
product.errors.each do |error|
puts "属性: #{error.attribute}, メッセージ: #{error.message}, 値: #{error.options[:value]}"
end
# 特定属性のエラー確認
if product.errors[:name].any?
puts "名前エラー: #{product.errors[:name].join(', ')}"
end
# エラー数
puts "エラー数: #{product.errors.count}"
# エラーの詳細情報
product.errors.details.each do |attribute, details|
puts "#{attribute}: #{details}"
end
end
# カスタムエラーメッセージ
class User < ApplicationRecord
validates :name, presence: { message: "名前を入力してください" }
validates :email, uniqueness: { message: "このメールアドレスは既に使用されています" }
validates :age, numericality: {
greater_than: 18,
message: "年齢は18歳以上である必要があります"
}
# プロック形式でのダイナミックメッセージ
validates :username, length: {
minimum: 3,
message: ->(object, data) do
"ユーザー名は#{data[:count]}文字以上入力してください(現在#{object.username&.length || 0}文字)"
end
}
end
# 国際化(I18n)設定
# config/locales/ja.yml
ja:
activerecord:
models:
user: "ユーザー"
product: "商品"
attributes:
user:
name: "名前"
email: "メールアドレス"
age: "年齢"
product:
name: "商品名"
price: "価格"
errors:
models:
user:
attributes:
email:
taken: "既に使用されているメールアドレスです"
invalid: "有効なメールアドレスを入力してください"
messages:
blank: "を入力してください"
too_short: "は%{count}文字以上で入力してください"
too_long: "は%{count}文字以下で入力してください"
not_a_number: "は数値で入力してください"
# エラーハンドリングユーティリティクラス
class ValidationErrorHandler
def self.format_errors(model)
return {} unless model.errors.any?
formatted_errors = {}
model.errors.each do |error|
attribute = error.attribute
formatted_errors[attribute] ||= []
formatted_errors[attribute] << {
message: error.message,
type: error.type,
options: error.options
}
end
formatted_errors
end
def self.errors_to_json(model)
{
success: false,
errors: format_errors(model),
error_count: model.errors.count
}.to_json
end
def self.first_error_message(model)
model.errors.full_messages.first
end
def self.errors_for_attribute(model, attribute)
model.errors[attribute].map do |message|
{
attribute: attribute,
message: message
}
end
end
end
# API応答での使用例
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
render json: {
success: true,
data: @user,
message: "ユーザーが正常に作成されました"
}, status: :created
else
render json: ValidationErrorHandler.errors_to_json(@user),
status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
# フォームでのエラー表示
# app/views/users/_form.html.erb
<%= form_with(model: user, local: true) do |form| %>
<% if user.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<h4><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h4>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name, "名前" %>
<%= form.text_field :name, class: "form-control #{'is-invalid' if user.errors[:name].any?}" %>
<% if user.errors[:name].any? %>
<div class="invalid-feedback">
<%= user.errors[:name].first %>
</div>
<% end %>
</div>
<div class="field">
<%= form.label :email, "メールアドレス" %>
<%= form.email_field :email, class: "form-control #{'is-invalid' if user.errors[:email].any?}" %>
<% if user.errors[:email].any? %>
<div class="invalid-feedback">
<%= user.errors[:email].join(', ') %>
</div>
<% end %>
</div>
<% end %>
Service Objectsとフォームオブジェクトでの活用
# Service Object パターン
class UserRegistrationService
include ActiveModel::Validations
include ActiveModel::AttributeAssignment
attr_accessor :name, :email, :password, :password_confirmation,
:terms_accepted, :newsletter_subscription
validates :name, presence: true, length: { minimum: 2 }
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, presence: true, length: { minimum: 8 }
validates :password_confirmation, presence: true
validates :terms_accepted, acceptance: true
validate :passwords_match
validate :email_not_taken
def initialize(params = {})
assign_attributes(params)
end
def call
return false unless valid?
ActiveRecord::Base.transaction do
user = create_user
send_welcome_email(user)
subscribe_to_newsletter(user) if newsletter_subscription
user
end
rescue => e
errors.add(:base, "登録処理中にエラーが発生しました: #{e.message}")
false
end
private
def passwords_match
return unless password.present? && password_confirmation.present?
unless password == password_confirmation
errors.add(:password_confirmation, "パスワードが一致しません")
end
end
def email_not_taken
return unless email.present?
if User.exists?(email: email)
errors.add(:email, "このメールアドレスは既に使用されています")
end
end
def create_user
User.create!(
name: name,
email: email,
password: password
)
end
def send_welcome_email(user)
UserMailer.welcome(user).deliver_now
end
def subscribe_to_newsletter(user)
NewsletterSubscriptionService.new(user).subscribe
end
end
# Form Object パターン
class ContactForm
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations
attribute :name, :string
attribute :email, :string
attribute :subject, :string
attribute :message, :string
attribute :category, :string
validates :name, presence: true, length: { maximum: 100 }
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :subject, presence: true, length: { minimum: 5, maximum: 200 }
validates :message, presence: true, length: { minimum: 10, maximum: 2000 }
validates :category, inclusion: { in: %w[general support sales technical] }
def submit
return false unless valid?
send_contact_email
save_contact_record
true
end
def persisted?
false
end
private
def send_contact_email
ContactMailer.new_inquiry(form_attributes).deliver_now
end
def save_contact_record
Contact.create!(form_attributes)
end
def form_attributes
{
name: name,
email: email,
subject: subject,
message: message,
category: category
}
end
end
# 複雑なビジネスルール用 Service Object
class OrderProcessingService
include ActiveModel::Validations
include ActiveModel::AttributeAssignment
attr_accessor :user, :items, :shipping_address, :payment_method,
:coupon_code, :delivery_date
validates :user, presence: true
validates :items, presence: true
validates :shipping_address, presence: true
validates :payment_method, inclusion: { in: %w[credit_card paypal bank_transfer] }
validate :items_valid
validate :shipping_address_valid
validate :payment_method_valid
validate :coupon_valid
validate :delivery_date_valid
validate :sufficient_inventory
validate :user_can_order
def initialize(params = {})
assign_attributes(params)
end
def process
return false unless valid?
ActiveRecord::Base.transaction do
order = create_order
process_payment(order)
update_inventory
send_confirmation_email(order)
order
end
rescue => e
errors.add(:base, "注文処理中にエラーが発生しました: #{e.message}")
false
end
private
def items_valid
return unless items.present?
items.each_with_index do |item, index|
unless item[:product_id].present?
errors.add(:items, "#{index + 1}番目の商品IDが指定されていません")
end
unless item[:quantity].present? && item[:quantity] > 0
errors.add(:items, "#{index + 1}番目の商品の数量が無効です")
end
end
end
def shipping_address_valid
return unless shipping_address.present?
required_fields = %w[name postal_code prefecture city address]
required_fields.each do |field|
unless shipping_address[field].present?
errors.add(:shipping_address, "#{field}が入力されていません")
end
end
end
def payment_method_valid
return unless payment_method.present?
case payment_method
when 'credit_card'
validate_credit_card
when 'paypal'
validate_paypal_account
when 'bank_transfer'
validate_bank_account
end
end
def coupon_valid
return unless coupon_code.present?
coupon = Coupon.find_by(code: coupon_code)
unless coupon&.valid_for_user?(user)
errors.add(:coupon_code, "無効なクーポンコードです")
end
end
def delivery_date_valid
return unless delivery_date.present?
if delivery_date <= Date.current
errors.add(:delivery_date, "配送日は今日以降を指定してください")
end
if delivery_date.saturday? || delivery_date.sunday?
errors.add(:delivery_date, "配送日は平日を指定してください")
end
end
def sufficient_inventory
return unless items.present?
items.each do |item|
product = Product.find(item[:product_id])
if product.stock_quantity < item[:quantity]
errors.add(:items, "#{product.name}の在庫が不足しています")
end
end
end
def user_can_order
return unless user.present?
if user.suspended?
errors.add(:user, "アカウントが停止されているため注文できません")
end
if user.unpaid_orders.count >= 3
errors.add(:user, "未払いの注文があるため新規注文できません")
end
end
def validate_credit_card
# クレジットカード検証ロジック
end
def validate_paypal_account
# PayPal検証ロジック
end
def validate_bank_account
# 銀行口座検証ロジック
end
def create_order
# 注文作成ロジック
end
def process_payment(order)
# 決済処理ロジック
end
def update_inventory
# 在庫更新ロジック
end
def send_confirmation_email(order)
# 確認メール送信ロジック
end
end
# 使用例
registration_service = UserRegistrationService.new(
name: "田中太郎",
email: "[email protected]",
password: "securepass123",
password_confirmation: "securepass123",
terms_accepted: "1",
newsletter_subscription: true
)
if registration_service.call
puts "ユーザー登録が完了しました"
else
puts "登録エラー:"
registration_service.errors.full_messages.each do |error|
puts "- #{error}"
end
end
# フォームオブジェクトの使用例(Controllerから)
class ContactsController < ApplicationController
def new
@contact_form = ContactForm.new
end
def create
@contact_form = ContactForm.new(contact_params)
if @contact_form.submit
redirect_to root_path, notice: "お問い合わせを送信しました"
else
render :new, status: :unprocessable_entity
end
end
private
def contact_params
params.require(:contact_form).permit(:name, :email, :subject, :message, :category)
end
end
テストでのバリデーション検証
# RSpec でのバリデーションテスト
RSpec.describe User, type: :model do
describe "validations" do
subject { build(:user) }
# 必須項目テスト
it { should validate_presence_of(:name) }
it { should validate_presence_of(:email) }
# 文字列長テスト
it { should validate_length_of(:name).is_at_least(2).is_at_most(50) }
it { should validate_length_of(:password).is_at_least(8) }
# 形式テスト
it { should allow_value("[email protected]").for(:email) }
it { should_not allow_value("invalid_email").for(:email) }
# 一意性テスト
it { should validate_uniqueness_of(:email).case_insensitive }
# 数値テスト
it { should validate_numericality_of(:age).is_greater_than(0).is_less_than(150) }
# 選択肢テスト
it { should validate_inclusion_of(:status).in_array(%w[active inactive pending]) }
# カスタムバリデーションテスト
describe "#password_complexity" do
it "requires uppercase letter" do
user = build(:user, password: "lowercase123!")
expect(user).not_to be_valid
expect(user.errors[:password]).to include("大文字を含む必要があります")
end
it "requires lowercase letter" do
user = build(:user, password: "UPPERCASE123!")
expect(user).not_to be_valid
expect(user.errors[:password]).to include("小文字を含む必要があります")
end
it "requires number" do
user = build(:user, password: "Password!")
expect(user).not_to be_valid
expect(user.errors[:password]).to include("数字を含む必要があります")
end
it "requires special character" do
user = build(:user, password: "Password123")
expect(user).not_to be_valid
expect(user.errors[:password]).to include("特殊文字を含む必要があります")
end
it "accepts valid complex password" do
user = build(:user, password: "ValidPass123!")
expect(user).to be_valid
end
end
# 条件付きバリデーションテスト
describe "conditional validations" do
context "when user is company type" do
it "requires tax_id" do
user = build(:user, user_type: "company", tax_id: nil)
expect(user).not_to be_valid
expect(user.errors[:tax_id]).to include("can't be blank")
end
end
context "when user is individual type" do
it "does not require tax_id" do
user = build(:user, user_type: "individual", tax_id: nil)
expect(user).to be_valid
end
end
end
end
# エラーメッセージテスト
describe "error messages" do
it "returns custom error message for invalid email" do
user = build(:user, email: "invalid")
user.valid?
expect(user.errors[:email]).to include("有効なメールアドレスを入力してください")
end
end
# バリデーションコンテキストテスト
describe "validation contexts" do
let(:user) { create(:user) }
it "validates current_password only on update context" do
expect(user).to be_valid
expect(user).not_to be_valid(:update)
expect(user.errors[:current_password]).to include("can't be blank")
end
end
end
# Minitest でのバリデーションテスト
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(
name: "テストユーザー",
email: "[email protected]",
password: "Password123!"
)
end
test "should be valid with valid attributes" do
assert @user.valid?
end
test "should require name" do
@user.name = nil
assert_not @user.valid?
assert_includes @user.errors[:name], "can't be blank"
end
test "should require valid email format" do
invalid_emails = %w[invalid @example.com test@ [email protected]]
invalid_emails.each do |email|
@user.email = email
assert_not @user.valid?, "#{email} should be invalid"
assert_includes @user.errors[:email], "有効なメールアドレスを入力してください"
end
end
test "should enforce unique email" do
duplicate_user = @user.dup
@user.save!
assert_not duplicate_user.valid?
assert_includes duplicate_user.errors[:email], "has already been taken"
end
test "password should meet complexity requirements" do
weak_passwords = ["password", "PASSWORD", "12345678", "Pass123"]
weak_passwords.each do |password|
@user.password = password
assert_not @user.valid?, "#{password} should be invalid"
end
end
end
# Factory Bot でのテストデータ作成
FactoryBot.define do
factory :user do
name { "テストユーザー" }
sequence(:email) { |n| "user#{n}@example.com" }
password { "ValidPass123!" }
password_confirmation { password }
age { 25 }
status { "active" }
trait :admin do
role { "admin" }
end
trait :company do
user_type { "company" }
tax_id { "1234567890" }
end
trait :invalid do
email { "invalid_email" }
end
end
end
# バリデーションテスト用のヘルパーメソッド
module ValidationTestHelpers
def assert_required_field(model, field)
model.send("#{field}=", nil)
assert_not model.valid?
assert_includes model.errors[field], "can't be blank"
end
def assert_field_length(model, field, min: nil, max: nil)
if min
model.send("#{field}=", "a" * (min - 1))
assert_not model.valid?
assert_includes model.errors[field], "is too short"
end
if max
model.send("#{field}=", "a" * (max + 1))
assert_not model.valid?
assert_includes model.errors[field], "is too long"
end
end
def assert_valid_formats(model, field, valid_values)
valid_values.each do |value|
model.send("#{field}=", value)
assert model.valid?, "#{value} should be valid for #{field}"
end
end
def assert_invalid_formats(model, field, invalid_values)
invalid_values.each do |value|
model.send("#{field}=", value)
assert_not model.valid?, "#{value} should be invalid for #{field}"
end
end
end