From 1dc805dd4d902b9204703f0bd6151c58f1f287af Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 19 Nov 2015 15:24:22 -0500 Subject: [PATCH] - add documentation that describes, then proceeds to warn about the many caveats and confusing effects of, the popular approach of using contains_eager() to alter the natural result of a related collection. I'm not a fan of this technique as it changes the semantics of a relationship in such a way that the rest of the ORM isn't aware of and it also can be undone very easily; hence the section needs as much text for warnings as for describing the technique itself. fixes #3563 --- doc/build/orm/loading_relationships.rst | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/build/orm/loading_relationships.rst b/doc/build/orm/loading_relationships.rst index 297392f3e5..3a0026bbe5 100644 --- a/doc/build/orm/loading_relationships.rst +++ b/doc/build/orm/loading_relationships.rst @@ -494,6 +494,50 @@ Or using the class-bound descriptor:: query(User).options(contains_eager(User.orders).contains_eager(Order.items)) +Using contains_eager() to load a custom-filtered collection result +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When we use :func:`.contains_eager`, *we* are constructing ourselves the +SQL that will be used to populate collections. From this, it naturally follows +that we can opt to **modify** what values the collection is intended to store, +by writing our SQL to load a subset of elements for collections or +scalar attributes. + +As an example, we can load a ``User`` object and eagerly load only particular +addresses into its ``.addresses`` collection just by filtering:: + + q = session.query(User).join(User.addresses).\ + filter(Address.email.like('%ed%')).\ + options(contains_eager(User.addresses)) + +The above query will load only ``User`` objects which contain at +least ``Address`` object that contains the substring ``'ed'`` in its +``email`` field; the ``User.addresses`` collection will contain **only** +these ``Address`` entries, and *not* any other ``Address`` entries that are +in fact associated with the collection. + +.. warning:: + + Keep in mind that when we load only a subset of objects into a collection, + that collection no longer represents what's actually in the database. If + we attempted to add entries to this collection, we might find ourselves + conflicting with entries that are already in the database but not locally + loaded. + + In addition, the **collection will fully reload normally** once the + object or attribute is expired. This expiration occurs whenever the + :meth:`.Session.commit`, :meth:`.Session.rollback` methods are used + assuming default session settings, or the :meth:`.Session.expire_all` + or :meth:`.Session.expire` methods are used. + + For these reasons, prefer returning separate fields in a tuple rather + than artificially altering a collection, when an object plus a custom + set of related objects is desired:: + + q = session.query(User, Address).join(User.addresses).\ + filter(Address.email.like('%ed%')) + + Advanced Usage with Arbitrary Statements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- 2.47.2