ActiveRecord モデル
Ruby on Rails
マイグレーション・バリデーション・コールバック
マイグレーション
テーブル作成・変更・カラム型
# マイグレーション生成
rails generate model User name:string email:string:uniq age:integer
rails generate migration AddPhoneToUsers phone:string
rails generate migration CreateJoinTableUsersRoles users roles
# カラム型
# string 短い文字列(255文字)
# text 長い文字列
# integer 整数
# bigint 大きな整数(ID に使用)
# float 浮動小数点
# decimal 精度指定の小数(金額など)
# boolean true/false
# date 日付
# datetime 日時
# timestamp タイムスタンプ
# json / jsonb JSON(PostgreSQL)
# references 外部キー(belongs_to と対応)class CreateUsers < ActiveRecord::Migration[8.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.string :password_digest
t.integer :age
t.decimal :balance, precision: 10, scale: 2, default: 0
t.boolean :active, default: true, null: false
t.text :bio
t.jsonb :preferences, default: {}
t.references :role, null: false, foreign_key: true
t.timestamps # created_at, updated_at を自動追加
end
add_index :users, :email, unique: true
add_index :users, [:name, :active] # 複合インデックス
end
end
# テーブルの変更
class AddPhoneToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :phone, :string
add_column :users, :deleted_at, :datetime
remove_column :users, :bio, :text
rename_column :users, :active, :is_active
change_column :users, :age, :bigint
add_index :users, :phone
remove_index :users, :phone
end
endバリデーション
validates・カスタムバリデーター・エラーメッセージ
class User < ApplicationRecord
# 存在チェック
validates :name, presence: true
validates :email, presence: true
# 長さ
validates :name, length: { minimum: 2, maximum: 50 }
validates :password, length: { in: 8..128 }
validates :bio, length: { maximum: 500 }, allow_blank: true
# 一意性
validates :email, uniqueness: { case_sensitive: false }
validates :username, uniqueness: { scope: :organization_id,
message: 'はこの組織内で重複しています' }
# フォーマット
VALID_EMAIL = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, format: { with: VALID_EMAIL }
# 数値
validates :age, numericality: { greater_than_or_equal_to: 0,
less_than: 150,
only_integer: true },
allow_nil: true
# 包含
validates :role, inclusion: { in: %w[user admin moderator] }
# 確認
validates :password, confirmation: true # password_confirmationフィールドを使用
# 条件付きバリデーション
validates :company, presence: true, if: :corporate_account?
validates :phone, presence: true, unless: -> { email.present? }
# カスタムバリデーター(メソッド)
validate :email_not_banned
private
def email_not_banned
if BannedEmail.exists?(domain: email.split('@').last)
errors.add(:email, 'のドメインは使用できません')
end
end
end
# バリデーションの確認
user = User.new(name: '', email: 'invalid')
user.valid? # false
user.invalid? # true
user.errors.full_messages # ['Name can't be blank', 'Email is invalid']
user.errors[:email] # ['is invalid']
user.save # false(バリデーション失敗)
user.save! # ActiveRecord::RecordInvalid を raiseコールバック
before_save・after_create・around_・条件付き
class Post < ApplicationRecord
belongs_to :user
# コールバックの種類
before_validation :normalize_title
after_validation :log_errors, if: :invalid?
before_save :set_slug
after_save :update_search_index
before_create :set_published_at
after_create :send_notification
after_create_commit :broadcast_post # トランザクションコミット後
before_update :track_changes
after_update :invalidate_cache
before_destroy :check_destroyable
after_destroy :cleanup_attachments
# around_コールバック
around_save :measure_save_time
private
def normalize_title
self.title = title.to_s.strip.capitalize
end
def set_slug
self.slug = title.parameterize if title_changed?
end
def set_published_at
self.published_at ||= Time.current if status == 'published'
end
def check_destroyable
if comments.exists?
errors.add(:base, 'コメントがあるため削除できません')
throw(:abort) # コールバックチェーンを中断・ロールバック
end
end
def measure_save_time
start = Time.current
yield # 保存処理を実行
Rails.logger.info "Save took #{Time.current - start}s"
end
end
# コールバックの実行順序(作成時)
# before_validation → after_validation
# → before_save → before_create
# → INSERT INTO ...
# → after_create → after_save
# → after_commit / after_create_commit