From: Mike Bayer Date: Sat, 23 Jan 2016 21:38:44 +0000 (-0500) Subject: - restate the "secondary" / AssociationProxy warning more strongly. X-Git-Tag: rel_1_1_0b1~84^2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=67a69da3aa86c8b2b560ed79a9c91408ad704879;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - restate the "secondary" / AssociationProxy warning more strongly. references #3638 --- diff --git a/doc/build/orm/basic_relationships.rst b/doc/build/orm/basic_relationships.rst index 97d3c48f4f..de156c2654 100644 --- a/doc/build/orm/basic_relationships.rst +++ b/doc/build/orm/basic_relationships.rst @@ -369,13 +369,55 @@ extension allows the configuration of attributes which will access two "hops" with a single access, one "hop" to the associated object, and a second to a target attribute. -.. note:: - - When using the association object pattern, it is advisable that the - association-mapped table not be used as the - :paramref:`~.relationship.secondary` argument on a - :func:`.relationship` elsewhere, unless that :func:`.relationship` - contains the option :paramref:`~.relationship.viewonly` set to - ``True``. SQLAlchemy otherwise may attempt to emit redundant INSERT - and DELETE statements on the same table, if similar state is - detected on the related attribute as well as the associated object. +.. warning:: + + The association object pattern **does not coordinate changes with a + separate relationship that maps the association table as "secondary"**. + + Below, changes made to ``Parent.children`` will not be coordinated + with changes made to ``Parent.child_associations`` or + ``Child.parent_associations`` in Python; while all of these relationships will continue + to function normally by themselves, changes on one will not show up in another + until the :class:`.Session` is expired, which normally occurs automatically + after :meth:`.Session.commit`:: + + class Association(Base): + __tablename__ = 'association' + + left_id = Column(Integer, ForeignKey('left.id'), primary_key=True) + right_id = Column(Integer, ForeignKey('right.id'), primary_key=True) + extra_data = Column(String(50)) + + child = relationship("Child", back_populates="parent_associations") + parent = relationship("Parent", back_populates="child_associations") + + class Parent(Base): + __tablename__ = 'left' + id = Column(Integer, primary_key=True) + + children = relationship("Child", secondary="association") + + class Child(Base): + __tablename__ = 'right' + id = Column(Integer, primary_key=True) + + Additionally, just as changes to one relationship aren't reflected in the + others automatically, writing the same data to both relationships will cause + conflicting INSERT or DELETE statements as well, such as below where we + establish the same relationship between a ``Parent`` and ``Child`` object + twice:: + + p1 = Parent() + c1 = Child() + p1.children.append(c1) + + # redundant, will cause a duplicate INSERT on Association + p1.parent_associations.append(Association(child=c1)) + + It's fine to use a mapping like the above if you know what + you're doing, though it may be a good idea to apply the ``viewonly=True`` parameter + to the "secondary" relationship to avoid the issue of redundant changes + being logged. However, to get a foolproof pattern that allows a simple + two-object ``Parent->Child`` relationship while still using the association + object pattern, use the association proxy extension + as documented at :ref:`associationproxy_toplevel`.