背景
- 添付ファイル機能を複数のモデル(チケット、メッセージ、コメントなど)に持たせたいケースがあった
- 愚直にやるとモデルごとに外部キーカラムを追加する必要があり、テーブル設計が肥大化する
- Railsのポリモーフィック関連(Polymorphic Association)を使えば、1つのテーブルで複数モデルへの紐づけを管理できると知ったのでまとめる
ポリモーフィック関連とは
1つのモデル(テーブル)が、複数の異なるモデルに対して belongs_to できる仕組み
通常の belongs_to は1つの親モデルに固定されるが、ポリモーフィックでは _type と _id の2カラムで「どのモデルの、どのレコードか」を表現する
通常の関連との違い
通常の関連(ポリモーフィック不使用の場合):
- モデルごとに別々の外部キーカラムが必要(例:
ticket_id,message_id,comment_id) - 紐づくモデルが増えるたびにカラム追加のマイグレーションが必要
- 大半のカラムが
NULLになり非効率
ポリモーフィック関連:
-
attachable_type(モデル名)とattachable_id(レコードID)の2カラムだけで管理 - 新しいモデルに添付機能を追加するときもマイグレーション不要
データの管理方法
attachmentsテーブルのデータイメージ:
| id | attachable_type | attachable_id | 意味 |
|---|---|---|---|
| 1 | "Ticket" | 10 | Ticket#10の添付 |
| 2 | "Message" | 25 | Message#25の添付 |
| 3 | "Ticket" | 10 | Ticket#10の2個目の添付 |
| 4 | "Comment" | 42 | Comment#42の添付 |
attachable_type にモデル名、attachable_id にそのモデルのレコードIDが入る。この2つの組み合わせで「どのモデルの、どのレコードに紐づくか」を一意に特定する
マイグレーション
class CreateAttachments < ActiveRecord::Migration[8.0]
def change
create_table :attachments do |t|
t.string :name
t.belongs_to :attachable, polymorphic: true
# ↑ これだけで attachable_type (string) と attachable_id (bigint) の
# 2カラム + インデックスが自動生成される
t.timestamps
end
end
end
t.belongs_to に polymorphic: true を渡すと、Railsが _type と _id の2カラムを自動的に作成する。手動で t.string :attachable_type と t.bigint :attachable_id を書く必要はない
Railsでの書き方
基本的なモデル定義
# belongs_to側(子モデル) — polymorphic: true を付ける
class Attachment < ApplicationRecord
belongs_to :attachable, polymorphic: true
end
# has_many側(親モデル) — as: :attachable で対応付け
class Ticket < ApplicationRecord
has_many :attachments, as: :attachable, dependent: :destroy
end
class Message < ApplicationRecord
has_many :attachments, as: :attachable, dependent: :destroy
end
-
belongs_to :attachable, polymorphic: true—attachable_typeとattachable_idの2カラムを使って、任意のモデルに紐づけることを宣言する -
has_many :attachments, as: :attachable— 「自分がattachableとして紐づけられているAttachmentを持つ」ことを宣言する。as:オプションがポリモーフィック関連のキーワード -
dependent: :destroy— 親レコードが削除されたとき、紐づくAttachmentも一緒に削除する
concernで共通化する
添付機能を使うモデルが複数ある場合、concernとして切り出すのが一般的
# app/models/concerns/attachable.rb
module Attachable
extend ActiveSupport::Concern
included do
has_many :attachments, as: :attachable, dependent: :destroy
end
end
# 使う側は include するだけ
class Ticket < ApplicationRecord
include Attachable
end
class Message < ApplicationRecord
include Attachable
end
class Comment < ApplicationRecord
include Attachable
end
ActiveSupport::Concern はRailsが提供するモジュール拡張の仕組みで、included ブロック内のコードは include された時点で実行される。これにより、新しいモデルに添付機能を追加するときは include Attachable の一行だけで済む
Active Storageと組み合わせた設計例
ポリモーフィック関連で添付管理モデルを作り、ファイル本体はActive Storageに任せるパターン
Ticket/Message/Comment
→ (polymorphic has_many) → Attachment
→ (Active Storage has_one_attached) → data_file (blob)
class Attachment < ApplicationRecord
belongs_to :attachable, polymorphic: true
has_one_attached :data_file # Active Storageでファイル本体を管理
end
has_one_attached はActive Storageのメソッドで、ファイルのアップロード・ダウンロード・削除をRailsの規約に沿って扱える。ポリモーフィック関連が「どのモデルに紐づくか」を管理し、Active Storageが「ファイル本体の保存先」を管理する、という役割分担になる
メリット・デメリット
| 内容 | |
|---|---|
| メリット | テーブル1つで済むためスキーマがシンプル。新しいモデルへの機能追加がマイグレーション不要(include するだけ)。concernと組み合わせることでコードをDRYに保てる |
| デメリット | DBレベルの外部キー制約が張れない(attachable_type が文字列のため、RDBMSは参照先テーブルを特定できない)。_type + _id の複合条件でJOINするためクエリが若干複雑になる |
外部キー制約が張れない問題について
通常の belongs_to では add_foreign_key :attachments, :tickets のようにDB側で参照整合性を保証できるが、ポリモーフィック関連では attachable_type が "Ticket" なのか "Message" なのかはアプリケーション層でしか判断できない。そのため、参照整合性はRailsの dependent: :destroy やバリデーションで担保する必要がある
ポリモーフィック関連が適しているケース
- 添付ファイル — 複数モデルにファイル添付機能を持たせたいとき
- コメント — チケット、記事、タスクなど複数の対象にコメント機能を付けたいとき
- タグ付け — 異なる種類のリソースに共通のタグ機能を提供したいとき
- いいね(Like) — 投稿、コメント、写真など複数の対象にリアクション機能を付けたいとき
共通点は「同じ構造の機能を、複数の異なるモデルで使いたい」というパターン
まとめ
- ポリモーフィック関連は「1つのテーブルで複数モデルへの紐づけを管理する仕組み」
-
_type+_idの2カラムで親モデルを特定する -
t.belongs_to :attachable, polymorphic: trueでマイグレーションを書き、belongs_to :attachable, polymorphic: trueとhas_many :attachments, as: :attachableでモデルを定義する - concernと組み合わせることで
include Attachableの一行で機能追加できる - DBレベルの外部キー制約が使えないトレードオフがあるため、参照整合性はアプリケーション層で担保する
感想
- concernとポリモーフィックが綺麗にハマるのが美しいなと思いました
参考
- Rails Guide - Polymorphic Associations — ポリモーフィック関連の公式ガイド
- Rails Guide - Active Storage — Active Storageの公式ガイド
- Rails API - ActiveSupport::Concern — concernの公式リファレンス