Polymorphism
プログラミングにはPolymorphismという考え方があり、Wkipediaには以下のように掲載されています。リンク
ポリモーフィズム(英: Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。
同様に、本来RDBでは一つのカラムは一つのテーブルに対してしかリレーションを持てないところを、複数のテーブル対してリレーションを持つ(複数の型に属する)ようなテーブルのことをPlymorphic Modelと言います。
SQLアンチパターンで紹介されているポリモーフィック関連
SQLアンチパターンという本をご存知でしょうか?RDBの設計やクエリのアンチパターンとその解決方法を紹介解説してくれています。
その中でポリモーフィック関連についても解説があったのでサンプルとして紹介します。
概要を解説するとBugs(バグ起票)テーブル、FeatureRequests(機能要望)テーブルがあり、それぞれにComments(コメント)をつけることができる。
Commentsのissue_idはBugsのbug_id、FeatureRequestsのrequesst_idが格納され、どちらのテーブルを紐付ているか判断するためにissue_typeに'Bugs'か'FeatureRequests'を格納する。
このとき実際に発行されるSQLやDBの外部キーが使えないことが、バグ等の原因になると言及されています。
Django Polymorphicの奇妙な動き
django-polymorphicとRails ActiveRecordを比較して挙動の差を確認できます。
チュートリアルに従ってそれぞれを実装すると以下のようになります。
Rails
modelファイル
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
belongs_to :
end
class Bug < ApplicationRecord
has_many :comments, as: :commentable
end
class FeatureRequest < ApplicationRecord
has_many :comments, as: :commentable
end
migrationファイル
class CreateComments < ActiveRecord::Migration[5.0]
def change
create_table :comments do |t|
t.string :comment
t.references :commentable, polymorphic: true
t.timestamps
end
end
end
CraeteとRead
# Create
Bug.create(title: "bug", description: "hello")
FeatureRequest.create(title: "feature request", description: "hello")
Comment.create(comment: 'comment on Bug', commentable_type: 'Bug', commentable_id: 1)
Comment.create(comment: 'comment on FeatureRequest', commentable_type: 'FeatureRequest', commentable_id: 1)
# Read
Bug.first.comments.first
#<Comment id: 1, comment: "comment on Bug", commentable_type: "Bug", commentable_id: 1, created_at: "2019-09-22 15:55:44", updated_at: "2019-09-22 15:55:44">
FeatureRequest.first.comments.first
#<Comment id: 1, comment: "comment on FeatureRequest", commentable_type: "FeatureRequest", commentable_id: 1, created_at: "2019-09-22 15:55:44", updated_at: "2019-09-22 15:55:44">
Django
from django.db import models
from polymorphic.models import PolymorphicModel
class Comment(PolymorphicModel):
id = models.BigAutoField(primary_key=True)
comment = models.TextField()
class Bug(Comment):
title = models.TextField()
description = models.TextField()
class FeatureRequest(Comment):
title = models.TextField()
description = models.TextField()
CreateとRead
# Create
Bug(comment='Bug', title="Bug title", description="Bug description").save()
FeatureRequest(comment='FeatureRequest', title="FeatureRequest title", description="FeatureRequest description").save()
# Read
Comment.objects.first()
# <Bug: Bug object (1)>
解説
Rails方はまさしくSQLアンチパターンの例のTableが生成されました。
SQLアンチパターンでも取り上げられているように、CommentとBug、CommentとFeatureRequestはそれぞれ外部結合してませんが、ActiveRecordによってそれを意識しなくてもいいようになっています。
それに対してDjangoのPolymorphicModelの挙動は全く異なります。
生成されるDBをERに起こすと以下のようになります。
Bugs、FeatureRequestはともにcomment_ptr_idでCommentsと結合しています。
これはBugs、FeatureRequestが共通の属性Commentsを継承(inherit)しているようです。
さらに一対一の関係になっているため、もともとやりたことはもやはできません。
感想
DjangoのPolymorphicModelはSQLアンチパターンで取り上げられているいわゆるポリモーフィック関連とは全く異なるということがわかりました。
もはやPolymorphicModelとポリモーフィック関連は別のものを指している?とさえ感じるレベルです。