Ответ 1
Самый простой шаблон, который я использую чаще всего, состоит в том, что у вас на самом деле есть отдельные таблицы комментариев для каждой связи. Это может показаться пугающим вначале, но он не требует дополнительного кода и не использует какой-либо другой подход - таблицы создаются автоматически, а модели относятся к шаблону Post.Comment
, Project.Comment
и т.д. Определение Комментарий сохраняется в одном месте. Этот подход с референтной точки зрения является самым простым и эффективным, а также большинством DBA, так как различные виды комментариев хранятся в собственных таблицах, размер которых может быть индивидуальным.
Еще один шаблон для использования - это одна таблица комментариев, но разные таблицы ассоциаций. Этот шаблон предлагает использовать случай, когда вам может понадобиться комментарий, связанный с более чем одним видом объекта за один раз (например, сообщение и проект одновременно). Этот шаблон по-прежнему достаточно эффективен.
В-третьих, существует таблица полиморфных ассоциаций. Этот шаблон использует фиксированное количество таблиц для представления коллекций и связанного с ними класса без ущерба для ссылочной целостности. Этот шаблон пытается приблизиться к "универсальному внешнему ключу" в стиле Django, сохраняя при этом ссылочную целостность, хотя и не так прост, как предыдущие два подхода.
Имитация шаблона, используемого ROR/Django, где нет реальных внешних ключей, и строки сопоставляются с использованием логики приложения, также возможно.
Первые три шаблона проиллюстрированы в современной форме в дистрибутиве SQLAlchemy в примерах /generic _associations/.
Шаблон 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)