Ruby on Rails

アソシエーション

Ruby on Rails

belongs_to・has_many・through・polymorphic

基本アソシエーション

belongs_to・has_many・has_one・オプション

app/models/associations.rb ruby
# === 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

polymorphic.rb ruby
# === ポリモーフィック ===
# 複数のモデルに対して同一の関連を持つ

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

association_methods.rb ruby
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 → 関連があれば例外