Rails のポリモーフィック関連付けの解説と方法
概要
現場でポリモーフィック関連付けを使用する機会がありましたので、記事を書きました。
オブジェクト指向のポリモーフィズムと絡めて勉強してみたので、もしも認識の違いなどあればコメントくださいませ。
ポリモーフィックと似ている、ポリモーフィズムについて
オブジェクト指向の三大要素の1つとして、「ポリモーフィズム」が存在します。
ポリモーフィズムとは、「多様性」という意味があり、同一のインターフェースやメソッド名を持つ異なるオブジェクトが、それぞれ異なる振る舞いをすることを言います。
例えば「犬」と「猫」のクラスを実装するとき、それらを抽象化した「動物」のクラスを実装することが多いはずです。
そして、犬と猫にそれぞれ「鳴く」というメソッドを実装するときは、「動物」クラスのメソッドをオーバーライドすることになります。
この「鳴くというメソッドが共通で存在し、クラスの種類(今回の「犬」と「猫」)によってそれぞれ異なる振る舞いをする」状態が、ポリモーフィズムの概念に当てはまると言えます。
class Animal
def make_sound # これがポリモーフィズムの概念に当てはまる。
raise "オーバーライドしてね"
end
end
class Dog < Animal
def make_sound
"ワン"
end
end
class Cat < Animal
def make_sound
"ニャー"
end
end
> Dog.new.make_sound
=> "ワン"
> Cat.new.make_sound
=> "ニャー"
このように、make_sound
メソッドは、異なるクラスに対して「多様性」を持つことができるわけです。
ポリモーフィック関連付け
先程のオブジェクト指向プログラミングのポリモーフィズムに対して、今回の Rails のポリモーフィック関連では、
「同じインターフェース(関連付け名)が共通で存在し、関連付けられているモデルの種類によって、それぞれ異なる振る舞いをする状態」がポリモーフィック関連付けです。
ポリモーフィック関連付けの手順
具体的な実装方法をみていきましょう。
今回は以下のような関連付けを作成していきます(わかりやすさのため、ポリモーフィック関連付けに関係するカラムのみを抽出しています)。
commentable_type
でどのモデルに関連しているかを判定できるようにしています。
共通の関連付け名は 〇〇able
とするのが一般的なようです。
migration の作成
comments テーブルに必要なカラムをもたせます。
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.text :content, null: false
t.references :commentable, polymorphic: true, null: false
t.timestamps
end
end
end
もしも既に article_id や post_id を持っている場合は、そのカラム名を変更します。
model にアソシエーションを追加
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true # これが異なるインターフェース間での共通の関連付け名
end
動作確認
動作確認のため、以下のようなデータを作成しておきます。
+----+-------------------+------------------+----------------+----------------------------+----------------------------+
| id | content | commentable_type | commentable_id | created_at | updated_at |
+----+-------------------+------------------+----------------+----------------------------+----------------------------+
| 1 | article commnet 1 | Article | 1 | 2024-01-09 23:19:54.000000 | 2024-01-09 23:19:56.000000 |
| 2 | article comment 2 | Article | 1 | 2024-01-09 23:22:58.000000 | 2024-01-09 23:23:00.000000 |
| 3 | article comment 3 | hogehoge | 1 | 2024-01-09 23:24:02.000000 | 2024-01-09 23:24:03.000000 |
| 4 | post comment 1 | Post | 1 | 2024-01-09 23:30:33.000000 | 2024-01-09 23:30:35.000000 |
| 5 | post comment 2 | Post | 1 | 2024-01-09 23:31:41.000000 | 2024-01-09 23:31:43.000000 |
+----+-------------------+------------------+----------------+----------------------------+----------------------------+
rails console で、 Article と Post それぞれに紐づいているオブジェクトが取得できていることを確認できました。
> Article.first.comments
=>
[#<Comment:0x0000ffff99c3e328
id: 1,
content: "article commnet 1",
commentable_type: "Article",
commentable_id: 1,
created_at: Tue, 09 Jan 2024 23:19:54.000000000 UTC +00:00,
updated_at: Tue, 09 Jan 2024 23:19:56.000000000 UTC +00:00>,
#<Comment:0x0000ffff99c3e120
id: 2,
content: "article comment 2",
commentable_type: "Article",
commentable_id: 1,
created_at: Tue, 09 Jan 2024 23:22:58.000000000 UTC +00:00,
updated_at: Tue, 09 Jan 2024 23:23:00.000000000 UTC +00:00>]
> Post.first.comments
=>
[#<Comment:0x0000ffff99c9dd50
id: 4,
content: "post comment 1",
commentable_type: "Post",
commentable_id: 1,
created_at: Tue, 09 Jan 2024 23:30:33.000000000 UTC +00:00,
updated_at: Tue, 09 Jan 2024 23:30:35.000000000 UTC +00:00>,
#<Comment:0x0000ffff99c9db48
id: 5,
content: "post comment 2",
commentable_type: "Post",
commentable_id: 1,
created_at: Tue, 09 Jan 2024 23:31:41.000000000 UTC +00:00,
updated_at: Tue, 09 Jan 2024 23:31:43.000000000 UTC +00:00>]
ちなみに流れている SQL は以下です。
commentable_id
と commentable_type
で検索しているのがわかりますね。
> Article.first.comments.to_sql
=> "SELECT `comments`.* FROM `comments` WHERE `comments`.`commentable_id` = 1 AND `comments`.`commentable_type` = 'Article'"
どんな時に便利か?
同じようなテーブル構造で、親のテーブルを共通にしたいときに使えます。
以下のようなテーブルの関係よりも、ポリモーフィック関連付けを使ったほうがシンプルですし、
コードの重複も少なくなります。
-
articles
テーブルに対応するarticle_comments
テーブル -
posts
テーブルに対応するpost_commnents
テーブル
Discussion