Как правильно моделировать наследование объединенной таблицы с помощью дискриминатора на основе роли в SQLAlchemy

1

Можно ли указать столбец дискриминатора из другой таблицы? Как это сделать с Declarative?

Причина этого заключается в том, что у меня есть объединенное наследование таблицы с помощью

class User(Base):
    id = Column(...)

class Customer(User):
    customer_id = Column('id', ...)

class Mechanic(User):
    mechanic_id = Column('id', ...)

class Role(Base):
    id = Column(..)

но хотите различать роли, которые пользователь имеет. Пользователь может иметь роль покупателя или роль продавца или и то, и другое.

Это правильный способ моделирования этого сценария?

В качестве примечания имеются конкретные данные о покупателе и данные о продавце, и именно поэтому я использую объединенное наследование таблицы.

  • 0
    Какие-либо предложения?
  • 0
    Что делать, если у пользователя есть роли «механик» и «клиент» одновременно?
Теги:
orm
data-modeling
sqlalchemy

2 ответа

3
Лучший ответ

Здесь неудобный и неэффективный способ достижения конкретного результата, который также потерпит неудачу, если на целевом пользователе присутствует более одной роли:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

role_to_user = Table('role_to_user', Base.metadata,
    Column('role_id', Integer, ForeignKey('role.id'), primary_key=True),
    Column('user_id', Integer, ForeignKey('user.id'), primary_key=True),
)

class Role(Base):
    __tablename__ = 'role'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)

    discrim = select([Role.name])
    discrim_col = discrim.label("foo")
    type = column_property(discrim_col)
    roles = relationship("Role", secondary=role_to_user)
    __mapper_args__ = {'polymorphic_on': discrim_col}

User.discrim.append_whereclause(Role.id==role_to_user.c.role_id)
User.discrim.append_whereclause(role_to_user.c.user_id==User.id)

class Customer(User):
    __tablename__ = 'customer'
    id = Column(Integer, ForeignKey(User.id), primary_key=True)
    __mapper_args__ = {'polymorphic_identity':'customer'}

class Mechanic(User):
    __tablename__ = 'mechanic'
    id = Column(Integer, ForeignKey(User.id), primary_key=True)

    __mapper_args__ = {'polymorphic_identity':'mechanic'}

e = create_engine('sqlite://', echo=True)

Base.metadata.create_all(e)

s = Session(e)

m, c = Role(name='mechanic'), Role(name='customer')
s.add_all([m, c])
s.add_all([Mechanic(roles=[m]), Customer(roles=[c])])
s.commit()

print Session(e).query(User).all()

Далее, вот как я это сделал бы, если бы у меня возникла проблема назначения поведения на основе нуля или более ролей - я бы поместил это поведение в Роль, а не в владельца роли:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    roles = relationship("Role", secondary= Table(
                    'role_to_user', Base.metadata,
                    Column('role_id', Integer, 
                            ForeignKey('role.id'), 
                            primary_key=True),
                    Column('user_id', Integer, 
                            ForeignKey('user.id'), 
                            primary_key=True),
                    ),
                    lazy="subquery"
                )

    def operate_on_roles(self):
        for role in self.roles:
            role.operate(self)

class Role(Base):
    __tablename__ = 'role'
    id = Column(Integer, primary_key=True)
    type = Column(String, nullable=False)
    __mapper_args__ = {'polymorphic_on': type}

class CustomerRole(Role):
    __tablename__ = 'customer'
    id = Column(Integer, ForeignKey(Role.id), primary_key=True)
    __mapper_args__ = {'polymorphic_identity':'customer'}

    def operate(self, user):
        print "user %s getting my car fixed!" % user.name

class MechanicRole(Role):
    __tablename__ = 'mechanic'
    id = Column(Integer, ForeignKey(Role.id), primary_key=True)
    __mapper_args__ = {'polymorphic_identity':'mechanic'}

    def operate(self, user):
        print "user %s fixing cars!" % user.name

e = create_engine('sqlite://', echo=True)

Base.metadata.create_all(e)

s = Session(e)

m, c = MechanicRole(), CustomerRole()
s.add_all([m, c])
s.add_all([
        User(name='u1', roles=[m, c]),
        User(name='u2', roles=[c]),
        User(name='u3', roles=[m]),
    ])
s.commit()

for user in s.query(User):
    user.operate_on_roles()

Второй пример выводит результат:

user u1 fixing cars!
user u1 getting my car fixed!
user u2 getting my car fixed!
user u3 fixing cars!

Как вы можете видеть, второй пример ясен и эффективен, а первый - просто мысленный эксперимент.

1

В самом общем случае я бы сказал "Нет".

В самом общем случае слова покупатель и продавец не описывают роли. Они описывают отношения между двумя сторонами. (Между двумя людьми, между двумя компаниями или между физическим лицом и компанией.) Отношения зависят от того, что одна сторона покупает хотя бы одну вещь у другой стороны.

Вам могут потребоваться таблицы покупателей и продавцов (с внешними ключами к таблице пользователей) для записи сведений, связанных только с покупателем или только с продавцом. Но при отсутствии таких подробностей таблица продаж, например {buyer_id, seller_id, product, date_time}, будет "обычным" способом записи того, кто купил то, что от кого.

  • 0
    Я отредактировал свое описание, чтобы оно было более конкретным. Я использовал покупателя и продавца раньше, потому что я просто хотел использовать общий случай. Цель ролей состоит не столько в описании отношений между ними, сколько в том, чтобы обозначить доступ к различным частям приложения. Для пояснения, роли - это отдельная таблица / класс от Покупателя / Продавца (Заказчик / Механик). Классы Покупатель / Продавец, как вы сказали, предназначены исключительно для данных Покупателя и Продавца.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню