Yannick Gingras wrote: > Greetings Alchemists, > this is more of a general data modeling question but maybe Alchemy has > a neat trick to resolve this issue. > > It happens quite often that I want to use instances of a class as > attributes of unrelated objects. One example is Addresses. Both > Companies and Persons have addresses and it would be somewhat awkward > to derive both from a common ancestor, even though mixins would do the > trick. However, the concept of mixins is not straightforward to > transpose to data mapping. The Address example could be implemented > as follow: > > class Address(DeclarativeBase): > __tablename__ = 'address' > id = Column(Integer, primary_key=True) > city = Column(Unicode(255)) > postal_code = Column(Unicode(15)) > # ... > > class Company(DeclarativeBase): > __tablename__ = 'company' > address_id = Column(Integer, ForeignKey('address.id')) > address = relation(Address) > > class Person(DeclarativeBase): > __tablename__ = 'person' > address_id = Column(Integer, ForeignKey('address.id')) > address = relation(Address) > > One problem with that is that it's hard to prevent orphan addresses. > Another thing that may or may not be a problem is that you could have > the same address being used for more than one company or person, which > could lead to a funny situation if one of them moves. > > To allow for more than one address per entity, one could do: > > class Company(DeclarativeBase): > __tablename__ = 'company' > shipping_address_id = Column(Integer, ForeignKey('address.id')) > shipping_address = relation(Address, primary_join=...) > billing_address_id = Column(Integer, ForeignKey('address.id')) > billing_address = relation(Address, primary_join=...) > > Similarly, an open ended number of addresses can be implemented with a > join table: > > class CompanyAddress(DeclarativeBase): > __tablename__ = 'company_address' > address_id = Column(Integer, ForeignKey('address.id')) > address = relation(Address, primary_join=...) > company_id = Column(Integer, ForeignKey('company.id')) > company_address = relation(Company, primary_join=...) > > class PersonAddress(DeclarativeBase): > __tablename__ = 'preson_address' > address_id = Column(Integer, ForeignKey('address.id')) > address = relation(Address, primary_join=...) > person_id = Column(Integer, ForeignKey('person.id')) > person_address = relation(Person, primary_join=...) > > But we still have the problem of orphan addresses and it get somewhat > tricky to prevent addresses re-use. > > It's easier to check for address re-use with a multi-slot join table: > > class AddressMap(DeclarativeBase): > __tablename__ = 'address_map' > address_id = Column(Integer, ForeignKey('address.id')) > address = relation(Address, primary_join=...) > company_id = Column(Integer, ForeignKey('company.id')) > company_address = relation(Company, primary_join=...) > person_id = Column(Integer, ForeignKey('person.id')) > person_address = relation(Person, primary_join=...) > > However, it's hard to extend when you need a new kind of objects that > has addresses. > > Is there a better solution? > > AFAIK the only part of SQLAlchemy that looks similar to your use case is concrete inheritance, but I believe even that will not address your concerns about orphan addresses and shared addresses.
I think your best solution is similar to your AddressMap idea above, but just make it part of Address instead with check and unique constraints on your FK columns: class Address(DeclarativeBase): __tablename__ = 'address' __table_args__ = ( CheckConstraint("CASE WHEN company_id IS NULL THEN 0 ELSE 1 END" " + CASE WHEN person_id IS NULL THEN 0 ELSE 1 END = 1"), UniqueConstraint("company_id"), UniqueConstraint("person_id")) # original columns go here... company_id = Column(Integer, ForeignKey('company.id', ondelete='CASCADE')) person_id = Column(Integer, ForeignKey('person.id', ondelete='CASCADE')) The check constraint above is a bit overkill for just 2 FK columns (you could just use "(company_id IS NULL) <> (person_id IS NULL)", but it is easy to extend to >2 FK columns. Hope it helps, -Conor --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---