Skip to content

ポリモーフィック関連を理解する: いいね機能やコメント機能を複数のモデルに対応させる方法

はじめに

Railsアプリケーションを開発していると、「コメント」や「いいね」、「タグ付け」といった機能を、複数の異なる種類のモデル(例えば、ブログの「記事」と「写真」の両方)に実装したいという要求が頻繁に発生します。

このとき、最も素朴なアプローチはArticleCommentモデルとPhotoCommentモデルのように、対象ごとに別々のモデルとテーブルを作成することですが、これではコメント機能に関するロジックが重複し、DRY原則に反してしまいます。かといって、一つのCommentモデルにarticle_idphoto_idという2つの外部キーを持たせるのも、拡張性に乏しく不格好です。

この問題をエレガントに解決するのが、Active Recordの「ポリモーフィック関連(Polymorphic Association)」です。ポリモーフィック(Polymorphic)とは「多様な形をとる」という意味で、その名の通り、1つのモデルが他の複数の異なるモデルに属することを可能にする強力な機能です。

ポリモーフィック関連の実装

ここでは、「記事(Article)」と「イベント(Event)」の両方に「コメント(Comment)」を付けられる機能を、ポリモーフィック関連を使って実装してみましょう。

ステップ1: ポリモーフィックなモデルの作成

まず、コメントを格納するCommentモデルを作成します。ここでのポイントは、特定のモデル(articleevent)を直接参照するのではなく、commentable という抽象的な名前でreferencesを指定し、{polymorphic: true}オプションを付けることです。

bash
rails g model Comment content:text commentable:references{polymorphic}

このコマンドが生成するマイグレーションファイルを見てみましょう。

ruby
# db/migrate/xxxxxxxx_create_comments.rb
class CreateComments < ActiveRecord::Migration[7.0]
  def change
    create_table :comments do |t|
      t.text :content
      t.references :commentable, polymorphic: true, null: false

      t.timestamps
    end
  end
end

t.references :commentable, polymorphic: trueという行に注目してください。これは、通常のarticle_idのような単一のカラムではなく、以下の2つのカラムcommentsテーブルに作成します。

  1. commentable_id (integer): 関連先のレコードのIDを保存します。
  2. commentable_type (string): 関連先のモデルのクラス名("Article""Event"など)を文字列として保存します。

この_id_typeのペアによって、1つのコメントがどのモデルのどのレコードに属しているのかを一意に特定できるようになります。

マイグレーションを実行します。

bash
rails db:migrate

ステップ2: モデル間の関連付け

次に、各モデルファイルに関連を定義します。

コメントされる側(親モデル)

ArticleモデルとEventモデルに、多数のコメントを持つことを示すhas_manyを定義します。ここでのポイントはas: :commentableオプションです。

ruby
# app/models/article.rb
class Article < ApplicationRecord
  has_many :comments, as: :commentable, dependent: :destroy
end
ruby
# app/models/event.rb
class Event < ApplicationRecord
  has_many :comments, as: :commentable, dependent: :destroy
end
  • as: :commentable: この関連が、commentable_idcommentable_typeというポリモーフィックなインターフェースを利用することを示します。

コメントする側(子モデル)

Commentモデルには、commentableという名前のポリモーフィックな関連に属することをbelongs_toで定義します。ジェネレータが既にこの設定を追加してくれています。

ruby
# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end
  • polymorphic: true: これにより、commentableメソッドが、commentable_typeカラムの値に基づいて、適切なモデル(ArticleEvent)のインスタンスを返すようになります。

3. 実際の使い方

設定はこれだけです。実際にコンソールでどのように動作するか見てみましょう。

ruby
# rails console

# 記事とイベントを作成
article = Article.create!(title: "ポリモーフィックは便利!")
event = Event.create!(name: "Rails勉強会")

# 記事にコメントを追加
comment1 = article.comments.create!(content: "本当にそうですね!")

# イベントにコメントを追加
comment2 = event.comments.create!(content: "参加します!")

データベースのcommentsテーブルの中身は以下のようになっています。

idcontentcommentable_idcommentable_type...
1本当にそうですね!1"Article"...
2参加します!1"Event"...

commentable_idは両方とも1ですが、commentable_typeが異なるため、それぞれが別のオブジェクトに関連付いていることがわかります。

逆に関連をたどることも簡単です。

ruby
comment1.commentable
#=> #<Article id: 1, title: "ポリモーフィックは便利!", ...>

comment2.commentable
#=> #<Event id: 1, name: "Rails勉強会", ...>

comment.commentableを呼び出すだけで、Commentモデルはcommentable_typeを見て、自動的に正しい親オブジェクトを返してくれます。

ポリモーフィック関連の一般的なユースケース

このパターンは非常に多くの場面で応用できます。

  • いいね機能 (Like): UserArticle, Photo, Commentなど、様々なものに「いいね」できる。
    • Likeモデル: belongs_to :likeable, polymorphic: true
    • Article, Photoモデル: has_many :likes, as: :likeable
  • タグ付け機能 (Tagging): TagArticle, Question, Productなど、様々なものに付けられる。
    • Taggingモデル: belongs_to :taggable, polymorphic: true
    • Article, Productモデル: has_many :taggings, as: :taggable
  • 住所録 (Address): User, Company, Warehouseなど、様々なエンティティが住所を持つことができる。
    • Addressモデル: belongs_to :addressable, polymorphic: true
    • User, Companyモデル: has_one :address, as: :addressable

まとめ

ポリモーフィック関連は、一見すると少し複雑に感じるかもしれませんが、その仕組みは「_idカラムと_typeカラムのペアで関連先を管理する」というシンプルなものです。

この強力な機能を使いこなすことで、モデル間の多様な関連性をDRYかつクリーンに表現でき、アプリケーションの設計をより柔軟で拡張性の高いものにすることができます。複数のモデルに共通の振る舞いを持たせたくなったときは、ぜひポリモーフィック関連の導入を検討してみてください。

AI が自動生成した技術記事をまとめたテックブログ