summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-11-27 11:18:55 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2013-11-27 11:18:55 -0500
commit33e77c3077a15c51f30ac5aae724c768b9a06911 (patch)
treee6c734df80491e35e75f7db44be9557cf75c92bc
parent30a50cc46aa836e24ebcbb889cbee583c511af82 (diff)
downloadsqlalchemy-33e77c3077a15c51f30ac5aae724c768b9a06911.tar.gz
- add more explicit docs for relationship mixin, [ticket:2876]
-rw-r--r--lib/sqlalchemy/ext/declarative/__init__.py54
1 files changed, 50 insertions, 4 deletions
diff --git a/lib/sqlalchemy/ext/declarative/__init__.py b/lib/sqlalchemy/ext/declarative/__init__.py
index b92adf02b..c60700a02 100644
--- a/lib/sqlalchemy/ext/declarative/__init__.py
+++ b/lib/sqlalchemy/ext/declarative/__init__.py
@@ -897,11 +897,57 @@ reference a common target class via many-to-one::
__tablename__ = 'target'
id = Column(Integer, primary_key=True)
+Using Advanced Relationship Arguments (e.g. ``primaryjoin``, etc.)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
:func:`~sqlalchemy.orm.relationship` definitions which require explicit
-primaryjoin, order_by etc. expressions should use the string forms
-for these arguments, so that they are evaluated as late as possible.
-To reference the mixin class in these expressions, use the given ``cls``
-to get its name::
+primaryjoin, order_by etc. expressions should in all but the most
+simplistic cases use **late bound** forms
+for these arguments, meaning, using either the string form or a lambda.
+The reason for this is that the related :class:`.Column` objects which are to
+be configured using ``@declared_attr`` are not available to another
+``@declared_attr`` attribute; while the methods will work and return new
+:class:`.Column` objects, those are not the :class:`.Column` objects that
+Declarative will be using as it calls the methods on its own, thus using
+*different* :class:`.Column` objects.
+
+The canonical example is the primaryjoin condition that depends upon
+another mixed-in column::
+
+ class RefTargetMixin(object):
+ @declared_attr
+ def target_id(cls):
+ return Column('target_id', ForeignKey('target.id'))
+
+ @declared_attr
+ def target(cls):
+ return relationship(Target,
+ primaryjoin=Target.id==cls.target_id # this is *incorrect*
+ )
+
+Mapping a class using the above mixin, we will get an error like::
+
+ sqlalchemy.exc.InvalidRequestError: this ForeignKey's parent column is not
+ yet associated with a Table.
+
+This is because the ``target_id`` :class:`.Column` we've called upon in our ``target()``
+method is not the same :class:`.Column` that declarative is actually going to map
+to our table.
+
+The condition above is resolved using a lambda::
+
+ class RefTargetMixin(object):
+ @declared_attr
+ def target_id(cls):
+ return Column('target_id', ForeignKey('target.id'))
+
+ @declared_attr
+ def target(cls):
+ return relationship(Target,
+ primaryjoin=lambda: Target.id==cls.target_id
+ )
+
+or alternatively, the string form (which ultmately generates a lambda)::
class RefTargetMixin(object):
@declared_attr