Open4

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

DANDAN

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 テーブルに新しい外部キーカラムを追加し、モデルのバリデーションを更新する必要がある。

DANDAN

ポリモーフィック関連を使う場合

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
DANDAN

デメリット

発行される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後からどんどん増えていきそう)な場合には、そもそもテーブルを分けた方が良い。