Open4
ポリモーフィック関連について

ArticleとRecipeというmodelがあり、それぞれにコメントを持ちたい時、ポリモーフィック関連を使わないと、以下の2つの実装方法が考えられる。
コメント用のmodelを複数作る
class ArticleComment < ApplicationRecord
belongs_to :article
end
class Article < ApplicationRecord
has_many :article_comments
end
class RecipeComment < ApplicationRecord
belongs_to :recipe
end
class Recipe < ApplicationRecord
has_many :recipe_comments
end
新しいモデル(例えば、Video や Book など)にコメントを関連付けるたびに、新しいコメントテーブルを作成する必要がある。またコメントのロジックや機能が似ているにもかかわらず、複数のテーブルを管理する必要があるため、DRY原則(重複を避ける原則)に反している可能性がある。
コメント用のmodelを1つだけ作り、複数の外部キーを持たせる
class Comment < ApplicationRecord
belongs_to :article, optional: true
belongs_to :recipe, optional: true
validate :only_one_commentable_association
private
def only_one_commentable_association
commentables = [article_id, recipe_id].compact
if commentables.count > 1
errors.add(:base, "コメントは1つのリソースにのみ関連付けることができます")
elsif commentables.count == 0
errors.add(:base, "コメントは何らかのリソースに関連付ける必要があります")
end
end
end
class Article < ApplicationRecord
has_many :comments
end
class Recipe < ApplicationRecord
has_many :comments
end
新しいコメント可能なモデルを追加するたびに、comments テーブルに新しい外部キーカラムを追加し、モデルのバリデーションを更新する必要がある。

ポリモーフィック関連を使う場合
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
class Recipe < ApplicationRecord
has_many :comments, as: :commentable
end

メリット

デメリット
発行されるSQLが複雑になる。
例えば以下のようなコードを書いた時
article = Article.find(1)
comments = article.comments
普通なら以下のSQLが発行される。
SELECT * FROM comments WHERE article_id = 1;
でもポリモーフィック関連を使っている場合は、以下のようなSQLになる。
SELECT * FROM comments WHERE commentable_type = 'Article' AND commentable_id = 1;
これくらいシンプルな処理ならあんまり気にならないけど、JOINとかがたくさん絡んできたりすると結構複雑になる。
データベースの外部キー制約が使えない
データベースの外部キー制約は、あるテーブルのカラムが、別のテーブルの特定のカラムの値を参照することを保証するもの。なので、一つのカラムに対して、一つのテーブルの特定のカラムを参照する制約を設定することが基本。
ポリモーフィック関連を使った場合、(上記の例でいうと)commentable_id
がarticles.idと一致することもあれば、recipes.idに一致することもあるため、外部キー制約が使えない。
拡張性
articleのコメントにだけ必要なカラム、recipeのコメントにだけ必要なカラムが多くある(or後からどんどん増えていきそう)な場合には、そもそもテーブルを分けた方が良い。