[Zope-dev] RFC: RelationAware class for relations between objects

Roché Compaan roche@upfrontsystems.co.za
Fri, 2 May 2003 13:58:42 +0200


On Thu, 1 May 2003 23:30:04 +0200
Roché Compaan <roche@upfrontsystems.co.za> wrote:

> Something like ComputedAttribute or descriptors should make it possible.
> Hmm, I might just have thought of a way to do this with
> ComputedAttribute which I'll try tomorrow. But ComputedAttribute is
> Zope2 specific isn't it? Darn ...

It looks like ComputedAttribute has no dependencies on Zope 2 code so I
had a go at another API addressing specifically the way in which objects
use relationships.

I used the Relations class in mxmRelations as is since it has no Zope
dependencies. How this will be made accessible as a different branch in
the ZODB (iow where it will be stored and how you will locate it) and
how the Relations API must be extended still needs to be addressed.

I assume all objects have ids. This is not a requirement and if we drop
this assumption very little in the implementation have to change. I do
not define Relationships as static attributes since we need a handle on
the class instance to relate.


from Relations import Relations # This is the Relations class in mxmRelations
import ExtensionClass
from ComputedAttribute import ComputedAttribute

class Relationship(ExtensionClass.Base):

    def __init__(self, ob, relation, cardinality):
        self.ob = ob
        self.relation = relation
        self.cardinality = cardinality

    _r_ = ComputedAttribute(lambda self: self.relation._get(self.ob))

    def __getattr__(self, name):
        l = self._r_
        if self.cardinality == 'single':
            return getattr(l[0], name)
        else:
            # This can optimised: Relations can return a BTree that we
            # can subscript
            for ob in l:
                if ob.id == name:
                    return ob

    def add(self, other):
        # TODO: test if 'other' is not a sequence if our cardinality is
        # single
        self.relation._relate(self.ob, other)

    def remove(self, other):
        self.relation._unrelate(self.ob, other)


student_courses = Relations()
term_courses = Relations()
# this relation will be between an object that knows it relationships and 
# instances of third party objects that doesn't
school_courses = Relations()

class Student:
    
    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.courses = Relationship(self, student_courses, 'multiple')

class Course:

    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.students = Relationship(self, student_courses, 'multiple') 
        self.school = Relationship(self, school_courses, 'single')

class Term:

    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.courses = Relationship(self, term_courses, 'multiple')

# This is a third party class
class School:

    def __init__(self, id, name):
        self.id = id
        self.name = name


john = Student('john', 'John Smith')
peter = Student('peter', 'Peter Pan')
mary = Student('mary', 'Mary Scary')
susan = Student('susan', 'Susan')

python101 = Course('python101', 'Python 101')
zope101 = Course('zope101', 'Zope 101')

law = School('law', 'Law')
compsci = School('compsci', 'Computer Science')

python101.students.add(john)
python101.students.add(peter)
python101.school.add(compsci)
zope101.students.add(mary)
zope101.students.add(peter)
zope101.school.add(compsci)

assert john.courses.python101.name == "Python 101"
assert mary.courses.zope101.name == "Zope 101"
assert python101.school.name == "Computer Science"

# Teach compsci about relationships
compsci.courses = Relationship(compsci, school_courses, 'multiple')
assert compsci.courses.python101.name == "Python 101"

# Changing attributes on related objects
python101.school.name = "Computer Science School"
zope101.students.mary.name = "Mary Airy"

-- 
Roché Compaan
Upfront Systems                 http://www.upfrontsystems.co.za