3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Djangoが他のPolymorphicと挙動が異なる件

Posted at

Polymorphism

プログラミングにはPolymorphismという考え方があり、Wkipediaには以下のように掲載されています。リンク

ポリモーフィズム(英: Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。

同様に、本来RDBでは一つのカラムは一つのテーブルに対してしかリレーションを持てないところを、複数のテーブル対してリレーションを持つ(複数の型に属する)ようなテーブルのことをPlymorphic Modelと言います。

SQLアンチパターンで紹介されているポリモーフィック関連

SQLアンチパターンという本をご存知でしょうか?RDBの設計やクエリのアンチパターンとその解決方法を紹介解説してくれています。
その中でポリモーフィック関連についても解説があったのでサンプルとして紹介します。

asdfasdf.png

概要を解説するとBugs(バグ起票)テーブル、FeatureRequests(機能要望)テーブルがあり、それぞれにComments(コメント)をつけることができる。
Commentsのissue_idはBugsのbug_id、FeatureRequestsのrequesst_idが格納され、どちらのテーブルを紐付ているか判断するためにissue_typeに'Bugs'か'FeatureRequests'を格納する。

このとき実際に発行されるSQLやDBの外部キーが使えないことが、バグ等の原因になると言及されています。

Django Polymorphicの奇妙な動き

django-polymorphicRails 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に起こすと以下のようになります。

aass.png

Bugs、FeatureRequestはともにcomment_ptr_idでCommentsと結合しています。
これはBugs、FeatureRequestが共通の属性Commentsを継承(inherit)しているようです。
さらに一対一の関係になっているため、もともとやりたことはもやはできません。

感想

DjangoのPolymorphicModelはSQLアンチパターンで取り上げられているいわゆるポリモーフィック関連とは全く異なるということがわかりました。
もはやPolymorphicModelとポリモーフィック関連は別のものを指している?とさえ感じるレベルです。

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?