summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-03-14 00:01:30 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-03-14 00:01:30 -0400
commit46ddbd344b41f24e1fc72a89f8613f4a2e3ec44e (patch)
treefe64bc67d648f667bf77807098a3a2d1bcfc9c4e
parent769fa67d842035dd852ab8b6a26ea3f110a51131 (diff)
downloadsqlalchemy-46ddbd344b41f24e1fc72a89f8613f4a2e3ec44e.tar.gz
restore an updated version of cascade backrefs
these sections were removed totally from SQLAlchemy 2.0 as the ``cascade_backrefs`` option was removed. However, the current behavior is still one that should be documented, so restore the two removed sections in a re-worded form that presents the current behavior as well as some explaination. Change-Id: I43fc0bca926a90937d419a1d11c27d18d5d5b4b8
-rw-r--r--doc/build/orm/backref.rst21
-rw-r--r--doc/build/orm/cascades.rst100
2 files changed, 121 insertions, 0 deletions
diff --git a/doc/build/orm/backref.rst b/doc/build/orm/backref.rst
index 2e1a1920c..3c1f7f8e6 100644
--- a/doc/build/orm/backref.rst
+++ b/doc/build/orm/backref.rst
@@ -187,6 +187,27 @@ it into a form that is interpreted by the receiving :func:`_orm.relationship` as
arguments to be applied to the new relationship it creates.
+Cascade behavior for backrefs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's important to note that even though a bi-directional relationship
+may be manipulated from either direction, having the same end effect on the
+object structure produced, there is a significant difference in how the
+:ref:`save-update cascade <cascade_save_update>` behaves for two objects
+where one is attached and the other is unattached to a :class:`_orm.Session`,
+depending on the direction in which the relationships are manipulated.
+
+The ``save-update`` cascade will only take effect **uni-directionally**
+in the direction from a parent object that is already associated with a
+:class:`_orm.Session`, towards an object that is being associated with that
+parent directly via an attribute or collection on that parent. It won't
+take effect if the parent object is instead assigned to an attribute or
+collection on the child, in which case the unattached parent object should be
+added to the :class:`_orm.Session` explicitly using :meth:`_orm.Session.add`.
+
+For a complete example of how this looks in practice, see the section
+:ref:`backref_cascade`.
+
One Way Backrefs
~~~~~~~~~~~~~~~~
diff --git a/doc/build/orm/cascades.rst b/doc/build/orm/cascades.rst
index 1f83fee50..c9e16cbac 100644
--- a/doc/build/orm/cascades.rst
+++ b/doc/build/orm/cascades.rst
@@ -122,6 +122,106 @@ for granted; it simplifies code by allowing a single call to
that :class:`.Session` at once. While it can be disabled, there
is usually not a need to do so.
+.. _backref_cascade:
+
+Behavior of save-update cascade with bi-directional relationships
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``save-update`` cascade takes place **uni-directionally** in the context of
+a bi-directional relationship, i.e. when using
+:ref:`backref / back_populates <relationships_backref>` to create two separate
+:func:`_orm.relationship` objects which refer to each other.
+
+An object that's not associated with a :class:`_orm.Session`, when assigned to
+an attribute or collection on a parent object that is associated with a
+:class:`_orm.Session`, will be automatically added to that same
+:class:`_orm.Session`. However, the same operation in reverse will not have
+this effect; an object that's not associated with a :class:`_orm.Session`, upon
+which a child object that is associated with a :class:`_orm.Session` is
+assigned, will not result in an automatic addition of that parent object to the
+:class:`_orm.Session`. The overall subject of this behavior is known
+as "cascade backrefs", and represents a change in behavior that was standardized
+as of SQLAlchemy 2.0.
+
+To illustrate, given a mapping of ``Order`` objects which relate
+bi-directionally to a series of ``Item`` objects via relationships
+``Order.items`` and ``Item.order``::
+
+ mapper_registry.map_imperatively(Order, order_table, properties={
+ 'items' : relationship(Item, back_populates='order')
+ })
+
+ mapper_registry.map_imperatively(Item, item_table, properties={
+ 'order' : relationship(Order, back_populates='items')
+ })
+
+If an ``Order`` is already associated with a :class:`_orm.Session`, and
+an ``Item`` object is then created and appended to the ``Order.items``
+collection of that ``Order``, the ``Item`` will be automatically cascaded
+into that same :class:`_orm.Session`::
+
+ >>> o1 = Order()
+ >>> session.add(o1)
+ >>> o1 in session
+ True
+
+ >>> i1 = Item()
+ >>> o1.items.append(i1)
+ >>> o1 is i1.order
+ True
+ >>> i1 in session
+ True
+
+Above, the bidirectional nature of ``Order.items`` and ``Item.order`` means
+that appending to ``Order.items`` also assigns to ``Item.order``. At the same
+time, the ``save-update`` cascade allowed for the ``Item`` object to be added
+to the same :class:`_orm.Session` which the parent ``Order`` was already
+associated.
+
+However, if the operation above is performed in the **reverse** direction,
+where ``Item.order`` is assigned rather than appending directly to
+``Order.item``, the cascade operation into the :class:`_orm.Session` will
+**not** take place automatically, even though the object assignments
+``Order.items`` and ``Item.order`` will be in the same state as in the
+previous example::
+
+ >>> o1 = Order()
+ >>> session.add(o1)
+ >>> o1 in session
+ True
+
+ >>> i1 = Item()
+ >>> i1.order = o1
+ >>> i1 in order.items
+ True
+ >>> i1 in session
+ False
+
+In the above case, after the ``Item`` object is created and all the desired
+state is set upon it, it should then be added to the :class:`_orm.Session`
+explicitly::
+
+ >>> session.add(i1)
+
+In older versions of SQLAlchemy, the save-update cascade would occur
+bidirectionally in all cases. It was then made optional using an option known
+as ``cascade_backrefs``. Finally, in SQLAlchemy 1.4 the old behavior was
+deprecated and the ``cascade_backrefs`` option was removed in SQLAlchemy 2.0.
+The rationale is that users generally do not find it intuitive that assigning
+to an attribute on an object, illustrated above as the assignment of
+``i1.order = o1``, would alter the persistence state of that object ``i1`` such
+that it's now pending within a :class:`_orm.Session`, and there would
+frequently be subsequent issues where autoflush would prematurely flush the
+object and cause errors, in those cases where the given object was still being
+constructed and wasn't in a ready state to be flushed. The option to select between
+uni-directional and bi-directional behvaiors was also removed, as this option
+created two slightly different ways of working, adding to the overall learning
+curve of the ORM as well as to the documentation and user support burden.
+
+.. seealso::
+
+ :ref:`change_5150` - background on the change in behavior for
+ "cascade backrefs"
.. _cascade_delete: