You are not logged in Log in Join
You are here: Home » Members » upfront » ZODBRelationships » FrontPage

Log in
Name

Password

 
 

History for FrontPage

??changed:
-
Problem

    Many people reject object-oriented databases because of the
    difficulty of expressing complex relationships. ZODB is based on the
    object oriented data model which does not address this problem.

    ZODB needs a reusable model for maintaining complex relationships
    between database objects. It also needs a basic implementation of
    that model.

    The solution must be built on a well-known model for maintaining
    relationships. If we invent a new model that is hard to map onto any
    existing model, it will be difficult to know the right way to expand
    the model as requirements grow. The entity-relationship model is an
    established model to express relationships between entities. This
    model can also be used to express relationships between objects in
    the ZODB.

    The solution must provide developers with ways to create
    relationships using only a few lines of Python code.

    The solution must provide a very Pythonic way to access and maintain
    relationships.

    Relationships should be bi-directional.

    Not all objects are designed with relationships in mind. That should
    not prevent them from participating in relationships.

    One should be able infer new relationships from existing ones.

Definitions

    To be clear we define a relationship as a set of bidirectional
    references which can be discovered by objects on either side of the
    relationship. We do not use the term "relation" because it has a
    strong association with a database table for some.

Proposal

    The implementation should be split up in two parts:

        - Storing relationships

        - Providing objects with views of relationships

    Storing relationships

        We store relationships in a central location because it enables
        us to infer new relationships without traversing the object
        system. This also makes it possible to store relationships
        between objects that are not designed with relationships in
        mind. 

        The developer does not need to create the relationship
        repository - it is globally available in a ZODB application. 

        An API will be provided for persisting relationships in
        ZODB. This API will create a relationship repository
        automatically if it does not already exist, enabling developers
        to mostly ignore the details of relationship storage.

        Each relationship is represented by a single Relationship
        object that needs to be created by the developer eg.::

            student_courses = Relationship()

    API for Relationship

        Max M's mxmRelations has a proven API for storing relationships
        and forms the basis for this API. The API has been modified to
        replace the word 'relation' with 'relationship'. Instances of
        Relationship are stored in a global relationship repository.

        ::

          class IRelationship(Interface):

              def relate(fromObjects, toObjects):
                  """
                  Create relationships for each object in 'fromObjects' to
                  each object in 'toObjects'. Both 'fromObjects' and
                  'toObjects' are sequences.
                  """

              def unrelate(fromObjects, toObjects):
                  """
                  Removes relationships between objects.
                  """

              def delete(obj):
                  """
                  Removes all relationships to the object.
                  Used if an object is deleted.
                  """

              def get(obj):
                  """
                  Returns all objects related to this object as a set, or
                  an empty set.
                  """
        
        Example::

            student_courses = Relationship()
            pete = Student()
            python101 = Course()
            zope101 = Course()
            student_courses.relate([pete], [python101, zope101])
            assert student_courses.get(pete) == [python101, zope101]
            assert student_courses.get(python101) == ![pete]
            student_courses.unrelate(pete, zope101)

    Providing objects with views of relationships (RelationshipViews)

        We use descriptors (or in ZODB3, ComputedAttributes) to provide
        an object-oriented view of relationships. The descriptor or
        computed attribute transparently accesses the relationship
        repository to provide relationships as attributes of the object.
        A relationship is defined at class level and is called a
        RelationshipView. It should be made clear that a class is not
        required to define relationships if it wants to participate in
        relationships, this is only necessary to provide relationships
        as attributes of the object. ( The example above illustrates how
        objects that are ignorant of relationships can be related. )

    Suggested implementation of a RelationshipView descriptor::

        class RelationshipView(object):

            def __init__(self, relationship, cardinality):
                self.relationship = relationship
                self.cardinality = cardinality

            def __get__(self, obj, cls=None):
                relatedObjects = self.relationship.get(obj)
                if self.cardinality == 'single':
                    return relatedObjects[0]
                else:
                    return relatedObjects

            def __set__(self, obj, value):
                if self.cardinality == 'single':
                    value = ![value]
                self.relationship.relate(obj, value)


    Example::

            student_courses = Relationship()
            course_teachers = Relationship()

            class Student:
                Courses = RelationshipView(student_courses,
                                           cardinality='multiple')

            class Course:
                Students = RelationshipView(student_courses,
                                            cardinality='multiple')
                Teacher = RelationshipView(course_teachers,
                                           cardinality='single')

            class Teacher:
                Courses = RelationshipView(course_teachers,
                                           cardinality='multiple')
                                        
            pete = Student()
            mary = Teacher()
            zope101 = Course()
            python101 = Course()

            pete.Courses.add(zope101)
            mary.Courses.add(python101)
            
            assert zope101.Teacher == mary
            assert python101.Students == ![pete]

            zope101.Teacher = mary
            assert mary.Courses == [python101, zope101]
            assert pete.Courses[0].Teacher == mary

Risk factors

    It might be a burden to support both ZODB 3 and ZODB 4.

Relationships and Metadata

    Not enough has been said about metadata about relationships. We need
    to gather more use cases and extend the Relationship API with a
    metadata API

<hr solid id=comments_below>


evan (May 9, 2003 3:23 pm; Comment #1)  --
 I disagree with several aspects of this approach.  Bi-directional relationships are inadequate for many applications, and even bidi is not enough for common cases without the ability to specify roles (i.e. am I the parent or the child?).
 
 Cardinality should be specified in, and introspectable from, the relationship itself, not the related objects.
 
[6 more lines...]