アソシエーション
Ruby on Rails
belongs_to・has_many・through・polymorphic
基本アソシエーション
belongs_to・has_many・has_one・オプション
# === belongs_to ===
class Comment < ApplicationRecord
belongs_to :post # post_id カラムが必要
belongs_to :user, optional: true # null 許可
belongs_to :author, class_name: 'User', foreign_key: :user_id
# カウンターキャッシュ
belongs_to :post, counter_cache: true # posts.comments_count を自動更新
end
# === has_many / has_one ===
class User < ApplicationRecord
has_one :profile, dependent: :destroy # 1対1
has_many :posts, dependent: :destroy # 1対多
has_many :comments
# ソート・条件付き
has_many :published_posts, -> { published.order(:title) },
class_name: 'Post'
has_many :recent_comments, -> { order(created_at: :desc).limit(5) },
class_name: 'Comment'
# 外部キーのカスタム
has_many :authored_posts, class_name: 'Post', foreign_key: :author_id
end
# === has_many :through ===
class Post < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
has_many :comments, dependent: :destroy
has_many :commenters, through: :comments, source: :user
end
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings
end
class Tagging < ApplicationRecord
belongs_to :post
belongs_to :tag
end
# has_one :through
class User < ApplicationRecord
has_one :account
has_one :account_history, through: :account
endポリモーフィックとSTI
polymorphic・Single Table Inheritance
# === ポリモーフィック ===
# 複数のモデルに対して同一の関連を持つ
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
# commentable_type: 'Post' / 'Video' / 'Photo'
# commentable_id: 対応するIDを格納
end
class Post < ApplicationRecord
has_many :comments, as: :commentable, dependent: :destroy
end
class Video < ApplicationRecord
has_many :comments, as: :commentable, dependent: :destroy
end
# マイグレーション
# add_reference :comments, :commentable, polymorphic: true, null: false
# 使用
post = Post.find(1)
post.comments.create!(body: '素晴らしい!', user: current_user)
Comment.where(commentable: post) # ポリモーフィックで検索
# === STI (Single Table Inheritance) ===
# 1つのテーブルで複数のクラスを管理
class Vehicle < ApplicationRecord
# type カラムが必要('Car', 'Truck', 'Motorcycle')
end
class Car < Vehicle
def fuel_type = '電気・ガソリン'
end
class Truck < Vehicle
def fuel_type = 'ディーゼル'
def max_load = '10t'
end
# 使用
Car.all # WHERE type = 'Car'
Vehicle.all # 全てのサブクラスを含む
car = Car.create!(name: 'Prius')
car.type # 'Car'
# === Delegated Types(Rails 6.1+、STIの代替)===
class Entry < ApplicationRecord
delegated_type :entryable, types: %w[Post Comment]
end
class Post < ApplicationRecord
include Entry::Entryable
endアソシエーションメソッド
build・create・コレクション操作・dependent
user = User.find(1)
# コレクションのビルド・作成
post = user.posts.build(title: '新記事') # 未保存
post = user.posts.create!(title: '新記事') # 保存(失敗で例外)
post = user.posts.create(title: '新記事') # 保存(失敗で false)
# コレクションの操作
user.posts << post # 追加
user.posts.delete(post) # 関連を削除(レコードは残る)
user.posts.destroy(post) # レコードごと削除
user.posts.clear # 全ての関連を解除
# 関連のクエリ
user.posts.where(status: 'published')
user.posts.order(created_at: :desc).limit(5)
user.posts.count
user.posts.exists?(id: 1)
user.posts.ids # 関連IDの配列
# 関連オブジェクトのリロード
user.posts.reload
# nested attributes
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses,
allow_destroy: true,
reject_if: :all_blank
end
# Strong Parameters で
def user_params
params.require(:user).permit(
:name,
addresses_attributes: [:id, :street, :city, :_destroy]
)
end
# dependent オプション
# :destroy → 各レコードの destroy を呼ぶ(コールバックあり)
# :delete_all → SQL DELETE(コールバックなし、高速)
# :nullify → 外部キーを NULL に
# :restrict_with_error → 関連があればエラー
# :restrict_with_exception → 関連があれば例外