私が最も頻繁に使用する最も単純なパターンは、関係ごとに個別のコメント テーブルを実際に持つというものです。これは最初は恐ろしく思えるかもしれませんが、他のアプローチを使用する場合と比較してコードを追加する必要はありません。テーブルは自動的に作成され、モデルはパターンPost.Comment
、Project.Comment
などを使用して参照されます。 Comment の定義は 1 か所で維持されます。 . 参照の観点からのこのアプローチは、最も単純で効率的であり、さまざまな種類のコメントが個別にサイズ変更できる独自のテーブルに保持されるため、DBA にとって最も使いやすいものです。
使用するもう 1 つのパターンは、単一のコメント テーブルですが、個別の関連付けテーブルです。このパターンは、一度に複数の種類のオブジェクトにリンクされたコメントが必要になる可能性があるユース ケースを提供します (同時に投稿とプロジェクトのように)。このパターンは依然としてかなり効率的です。
3 つ目は、ポリモーフィック アソシエーション テーブルです。このパターンでは、固定数のテーブルを使用して、参照整合性を犠牲にすることなく、コレクションと関連するクラスを表します。このパターンは、前の 2 つのアプローチほど単純ではありませんが、参照整合性を維持しながら、Django スタイルの「ジェネリック外部キー」に最も近づけようとします。
実際の外部キーが使用されず、アプリケーション ロジックを使用して行が照合される、ROR/Django で使用されるパターンを模倣することも可能です。
最初の 3 つのパターンは、examples/generic_associations/の下の SQLAlchemy ディストリビューションに現代的な形式で示されています。
ROR/Django パターンについては、よく聞かれるので、あまり好きではありませんが、SQLAlchemy の例にも追加します。私が使用しているアプローチは、Django が行うものとまったく同じではありません。型を追跡するために「contenttypes」テーブルを使用しているように見えるためです。識別子列に基づく任意の数のテーブルを指します。ここにあります:
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import create_engine, Integer, Column, \
String, and_
from sqlalchemy.orm import Session, relationship, foreign, remote, backref
from sqlalchemy import event
class Base(object):
"""Base class which provides automated table name
and surrogate primary key column.
"""
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class Address(Base):
"""The Address class.
This represents all address records in a
single table.
"""
street = Column(String)
city = Column(String)
zip = Column(String)
discriminator = Column(String)
"""Refers to the type of parent."""
parent_id = Column(Integer)
"""Refers to the primary key of the parent.
This could refer to any table.
"""
@property
def parent(self):
"""Provides in-Python access to the "parent" by choosing
the appropriate relationship.
"""
return getattr(self, "parent_%s" % self.discriminator)
def __repr__(self):
return "%s(street=%r, city=%r, zip=%r)" % \
(self.__class__.__name__, self.street,
self.city, self.zip)
class HasAddresses(object):
"""HasAddresses mixin, creates a relationship to
the address_association table for each parent.
"""
@event.listens_for(HasAddresses, "mapper_configured", propagate=True)
def setup_listener(mapper, class_):
name = class_.__name__
discriminator = name.lower()
class_.addresses = relationship(Address,
primaryjoin=and_(
class_.id == foreign(remote(Address.parent_id)),
Address.discriminator == discriminator
),
backref=backref(
"parent_%s" % discriminator,
primaryjoin=remote(class_.id) == foreign(Address.parent_id)
)
)
@event.listens_for(class_.addresses, "append")
def append_address(target, value, initiator):
value.discriminator = discriminator
class Customer(HasAddresses, Base):
name = Column(String)
class Supplier(HasAddresses, Base):
company_name = Column(String)
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
session.add_all([
Customer(
name='customer 1',
addresses=[
Address(
street='123 anywhere street',
city="New York",
zip="10110"),
Address(
street='40 main street',
city="San Francisco",
zip="95732")
]
),
Supplier(
company_name="Ace Hammers",
addresses=[
Address(
street='2569 west elm',
city="Detroit",
zip="56785")
]
),
])
session.commit()
for customer in session.query(Customer):
for address in customer.addresses:
print(address)
print(address.parent)