2013-07-17 14 views
12

Czy sqlalchemy ma coś takiego jak GenericForeignKey django? I czy słuszne jest używanie generycznych obcych pól.Ogólny klucz obcy sqlalchemy (jak w ORM django)

Mój problem: mam kilka modeli (na przykład Post, Project, Vacancy, nic specjalnego) i chcę dodać komentarze do każdego z nich. Chcę używać tylko jednego modelu komentarza. Czy warto? Czy powinienem użyć PostComment, ProjectComment itp.? Plusy/minusy obu sposobów?

Dzięki!

Odpowiedz

13

Najprostszym wzorcem, którego używam najczęściej, jest to, że masz osobne tabele komentarzy dla każdej relacji. Na początku może to wydawać się przerażające, ale nie powoduje żadnego dodatkowego kodu w porównaniu z innym podejściem - tabele są tworzone automatycznie, a modele są określane przy użyciu wzorca Post.Comment, Project.Comment itp. Definicja komentarza jest utrzymywana w jedno miejsce. To podejście z referencyjnego punktu widzenia jest najbardziej proste i wydajne, a także najbardziej przyjazne dla DBA, ponieważ różne rodzaje komentarzy przechowywane są w ich własnych tabelach, które można indywidualnie dopasować.

Kolejny wzorzec do użycia to pojedyncza tabela komentarzy, ale odrębne tabele powiązań. Ten wzorzec oferuje przypadek użycia, że ​​możesz chcieć, aby komentarz był powiązany z więcej niż jednym rodzajem obiektu naraz (jak post i projekt w tym samym czasie). Ten wzór jest nadal dość skuteczny.

Po trzecie, istnieje tabela asocjacji polimorficznej. Ten wzorzec wykorzystuje stałą liczbę tabel do reprezentowania kolekcji i odpowiedniej klasy bez poświęcania integralności referencyjnej. Ten wzorzec stara się być najbliższy "ogólnemu kluczowi obcemu" w stylu Django, zachowując przy tym integralność referencyjną, choć nie jest to tak proste, jak w poprzednich dwóch podejściach.

Naśladowanie wzoru używanego przez ROR/Django, gdzie nie są używane rzeczywiste klucze obce i wiersze są dopasowywane za pomocą logiki aplikacji, jest również możliwe.

Pierwsze trzy wzory zostały zilustrowane w nowoczesnej formie w rozkładzie SQLAlchemy w ramach przykładów/generic_associations /.

Wzorzec ROR/Django, ponieważ często się o to pyta, dodam także do przykładów SQLAlchemy, mimo że nie podoba mi się to zbytnio. Podejście, którego używam, nie jest dokładnie takie samo jak w przypadku Django, ponieważ wydaje się, że używa tabeli typu "contenttypes" do śledzenia typów, co wydaje się dla mnie zbyteczne, ale ogólna koncepcja kolumny całkowitej, która wskazuje na dowolną liczbę tabel opartych na kolumnie dyskryminatora. Tutaj jest:

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) 
+0

Dziękujemy! Nie podoba mi się pierwszy wzór - myślę, że nie jest SUCHY. Jeśli chcę dodać trochę informacji do każdego komentarza (może to być flaga 'edited') powinienem to zrobić ze wszystkimi modelami/tabelami. Myślałem o drugim wzorze mojego modelu "Tag". Może być połączony z 'Project' i' Post' w tym samym czasie. I trzeci wydaje się być tym, czego potrzebuję do mojego "komentarza". ROR/Django wydaje się nie być tak prosty, więc go "studiuję". – krasulya

+1

jest całkowicie SUCHA. DRY oznacza "nie powtarzaj się". Jeśli spojrzysz na to, jak wzór działa, w ogóle się nie powtarzasz. To, że w DB jest dużo podobnych tabel, nie oznacza, że ​​się powtarzasz; ich tworzenie jest zautomatyzowane, podobnie jak dodanie nowej kolumny, takiej jak "edytowane" (za pomocą narzędzia takiego jak Alembic). Jest to najbardziej przestrzenne i czasochłonne i przyjazne dla DBA podejście (ponieważ pamięć dla różnych tabel może być konfigurowana niezależnie). Szkoda, że ​​mam tyle problemów z przekonaniem byłych djangorzy. – zzzeek

+0

Podoba mi się pomysł twojego pierwszego rozwiązania, ale nie wiem, jak go właściwie wdrożyć. Czy możesz podać krótki przykład? – aquavitae

0

Wiem, że to jest prawdopodobnie straszny sposób, ale to było szybkie rozwiązanie dla mnie.

class GenericRelation(object): 
def __init__(self, object_id, object_type): 
    self.object_id = object_id 
    self.object_type = object_type 

def __composite_values__(self): 
    return (self.object_id, self.object_type) 


class Permission(AbstractBase): 

#__abstract__ = True 

_object = None 

_generic = composite(
    GenericRelation, 
    sql.Column('object_id', data_types.UUID, nullable=False), 
    sql.Column('object_type', sql.String, nullable=False), 
) 

permission_type = sql.Column(sql.Integer) 

@property 
def object(self): 
    session = object_session(self) 
    if self._object or not session: 
     return self._object 
    else: 
     object_class = eval(self.object_type) 
     self._object = session.query(object_class).filter(object_class.id == self.object_id).first() 
     return self._object 

@object.setter 
def object(self, value): 
    self._object = value 
    self.object_type = value.__class__.__name__ 
    self.object_id = value.id