Relations
=========

Relations also need to be managed by a workflow system. Clio knows how
to handle objects that have relations, if at least we define these
relations in a special manner.

One to Many Relations
---------------------

Normally when creating a relation using the SQLAlchemy ORM, you use
the ``relation`` constructor in the mapper. With Clio you have to use
``dynamic_loader`` instead. This is also supplied by SQLAlchemy and
receives the same parameters as ``relation``. The difference is that
``dynamic_loader`` easily allows further filtering to be done on the
generated relation properties, which Clio uses to set up its
workflow-aware properties. ``dynamic_loader`` generates properties
that are queries, not true collections.

We define two Clio tables with a foreign key relation between them::

  jar = clio.Table('jar', metadata,
     Column(name, Unicode(20), nullable=False))

  cookie = clio.Table('cookie', metadata,
     Column(name, Unicode(20), nullable=False),
     Column('jar_id', Integer, ForeignKey('jar.id'), nullable=False))

We can now set up two models that represent these tables::
 
  class Jar(clio.Model):
      def __init__(self, code, name):
          super(Jar, self).__init__(code)
          self.name = name

  class Cookie(clio.Model):
      def __init__(self, code, name):
          super(Cookie, self).__init__(code)
          self.name = name

We map the classes to the tables and define a relation::

  mapper(Jar, jar,
         properties={
           'cookies': dynamic_loader(Cookie, backref('jar')),
         })

``Jar`` instances will now have a ``cookies`` property on it. This
property is not a collection like you would get if you had used
``relation``, but a SQLAlchemy query object. You can loop through it
and access individual items like you would with a Python ``list``, but
other operations (like ``len``) won't work. To treat it as a list, you
can however always simply use ``list`` on it::

  list(somejar.cookies)

This executes the query and puts the resulting objects in a list.

Workflow properties
-------------------

The ``cookies`` property represents *all* cookies that are in the jar,
new, published or archived. Clio provides other relation properties
that give just the published relations, the archived relations or the
newly created or under edit relations (or the under edit relations and
those relations that were deleted). To establish those properties, you
need to call ``clio.workflow_properties`` on the mapped classes::

  clio.workflow_properties(Jar)
  clio.workflow_properties(Cookie)

Now you can access the following extra properties::

  somejar.cookies_archived
  somejar.cookies_published
  somejar.cookies_editable

A special ``cookies_original`` relation also exists. This is because
the ``cookies`` relation has special behavior for ``UPDATABLE``
versions (it refers back to the published version to retrieve relation
information).

Many to Many Relations
----------------------

The other major type of relation that Clio supports is the
many-to-many relation. In this case a pivot (or secondary) table
exists that describes a many to many relationship between two other
tables::

  bird = clio.Table('bird', metadata,
     Column(name, Unicode(20), nullable=False))

  feeding_site = clio.Table('feeding_site', metadata,
     Column(name, Unicode(20), nullable=False))

  from sqlalchemy import Table

  # the secondary table
  bird_feeding_site = Table('bird_feeding_site', metadata,
     Column('bird_id', Integer, ForeignKey('bird.id'), nullable=False),
     Column('feeding_site_id', Integer, ForeignKey('feeding_site.id'), nullable=False))

Note that the pivot table is not defined as a Clio table but is
instead a normal SQLALchemy table.

Next, we set up the classes to be mapped by the ORM::

  class Bird(clio.Model):
      def __init__(self, code, name):
          super(Bird, self).__init__(code)
          self.name = name

  class FeedingSite(clio.Model):
      def __init__(self, code, name):
          super(FeedingSite, self).__init__(code)
          self.name = name
   
And we set up the mapping itself::

  mapper(Bird, bird)

  mapper(FeedingSite, feeding_site,
         properties={
           'birds': dynamic_loader(
              Bird,
              secondary=bird_feeding_site,
              backref=backref('feeding_sites', lazy='dynamic')),
          })

Again, we use ``dynamic_loader``. This time we also need to supply a
special parameter to the ``backref``, namely ``lazy='dynamic'``. This
is to ensure that the ``feeding_sites`` properties generated on the
``Bird`` class is also a ``dynamic_loader`` relation.

Finally we need to set up the workflow properties::

  clio.workflow_properties(Bird)
  clio.workflow_properties(FeedingSite)
