Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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とポリモーフィック関連は別のものを指している?とさえ感じるレベルです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away