]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add more explicit docs for relationship mixin, [ticket:2876]
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 27 Nov 2013 16:18:55 +0000 (11:18 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 27 Nov 2013 16:18:55 +0000 (11:18 -0500)
lib/sqlalchemy/ext/declarative/__init__.py

index b92adf02b62e40bf7cdae584c46ec0fdbde96f42..c60700a020489001637d418ff34795912f218498 100644 (file)
@@ -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