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
somejar.cookies_actual

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)

Table Of Contents

Previous topic

Clio Models

Next topic

Workflow

This Page