Railsのポリモーフィック関連で中間テーブルを減らす実装例(before/after)
大学時代に金沢の観光情報サービスを個人開発していたとき、「カフェにも観光スポットにも同じタグを付けて検索できるようにしたい」という要件で設計に詰まりました。素直に実装すると中間テーブルが2本になり、タグ側からの検索も面倒になります。そこでポリモーフィック関連を使ってすっきりさせたので、その before/after を残しておきます。
解決したかった課題
Cafe(カフェ)と Spot(観光スポット)のどちらにも SearchTag(検索タグ)を紐付けたい、という要件でした。1件のカフェや観光スポットに複数のタグが付くので、多対多の関連が必要になります。
Before: 中間テーブルを2本作る方法
ポリモーフィック関連を使わない場合、モデルごとに中間テーブルが必要になります。
class Cafe < ApplicationRecord
has_many :search_tags, through: :cafe_search_tags
has_many :cafe_search_tags
end
class Spot < ApplicationRecord
has_many :search_tags, through: :spot_search_tags
has_many :spot_search_tags
end
class CafeSearchTag < ApplicationRecord
belongs_to :cafe
belongs_to :search_tag
end
class SpotSearchTag < ApplicationRecord
belongs_to :spot
belongs_to :search_tag
end
class SearchTag < ApplicationRecord
has_many :cafe_search_tags
has_many :spot_search_tags
end
この構成には2つの問題があります。
1つは、モデルが増えるたびに中間テーブルが1本ずつ増えていくことです。仮に Hotel や Restaurant を追加したくなると、HotelSearchTag・RestaurantSearchTag をそれぞれ作ることになります。
もう1つは、SearchTag 側から「このタグが付いているカフェと観光スポットをまとめて取得したい」という操作が難しくなることです。cafe_search_tags と spot_search_tags を別々に問い合わせてマージする、という手間が生じます。
After: ポリモーフィック関連を使う方法
ポリモーフィック関連を使うと、中間テーブルを1本に統一できます。
class Cafe < ApplicationRecord
has_many :search_tags, through: :destination_search_tags
has_many :destination_search_tags, as: :destinationable
end
class Spot < ApplicationRecord
has_many :search_tags, through: :destination_search_tags
has_many :destination_search_tags, as: :destinationable
end
class DestinationSearchTag < ApplicationRecord
belongs_to :destinationable, polymorphic: true
belongs_to :search_tag
end
class SearchTag < ApplicationRecord
has_many :destination_search_tags
end
中間テーブルは DestinationSearchTag の1本だけです。belongs_to :destinationable, polymorphic: true と宣言することで、destinationable_type(モデル名の文字列)と destinationable_id(ID)の2カラムが保存され、Cafe か Spot かを問わず同じテーブルで関連を管理できます。
Cafe 側と Spot 側では has_many :destination_search_tags, as: :destinationable と書くだけで、あとの扱いは通常の多対多と同じです。cafe.search_tags や spot.search_tags でそれぞれのタグを取得できます。
マイグレーションの書き方
DestinationSearchTag のマイグレーションでは、ポリモーフィック用の2カラムをまとめて宣言できます。
class CreateDestinationSearchTags < ActiveRecord::Migration[6.1]
def change
create_table :destination_search_tags do |t|
t.references :destinationable, polymorphic: true, null: false
t.references :search_tag, null: false, foreign_key: true
t.timestamps
end
end
end
t.references :destinationable, polymorphic: true と書くと、destinationable_type と destinationable_id の両カラムとインデックスが自動で作成されます。
バージョン・公式リファレンス
ポリモーフィック関連付けは Rails の初期から提供されている機能で、belongs_to :xxx, polymorphic: true と as: オプションの書き方は現行の Rails でも変わりません。マイグレーションで t.references :xxx, polymorphic: true を使う記法(上記コード例で使用)は Rails 4 以降で利用できます。お使いの Rails バージョンに合わせて、下記の公式ガイドで確認してください。

Active Record の関連付け - Railsガイド
Active Recordが提供するすべての関連付け機能(アソシエーション)について解説します。
参考

Railsのポリモーフィック関連とはなんなのか - Qiita
はじめに Railsのポリモーフィック(polymorphic、多態性)関連について、実装方法を説明している記事は見かけるのですが、実際これがどんなものでどんな時に便利なのかを説明している記事があまりなく、よくわからないで使っている人もいるようなので本記事を書きました。 ...

Railsのポリモーフィック関連 初心者→中級者へのSTEP10/25 - Qiita
Railsのポリモーフィック関連 はじめに 今回はポリモーフィック関連についてです。 はて、ポリモーフィックとは?の方ように、ポリモーフィックについて簡単に説明して、実際に実装してみたいと思います。 ポリモーフィズムとは## Polymorphism、意味は多様性。オブ...
記事の更新をメールで受け取る
質問・リクエストを送る
記事についての質問や、取り上げてほしいテーマがあればお気軽にどうぞ。いただいた質問はブログ記事として回答し、Q&Aページで公開することがあります。