cybertools/relation
helmutm 3c8b67a378 more on relations - now includes an IObjectRemoved event handler and more fine-grained permissions
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@657 fd906abe-77d9-0310-91a1-e0d9ade77398
2005-11-05 14:13:47 +00:00
..
__init__.py work in progress: cybertools.relation package 2005-11-02 08:00:17 +00:00
configure.zcml more on relations - now includes an IObjectRemoved event handler and more fine-grained permissions 2005-11-05 14:13:47 +00:00
ftests.py added functional test to relation package 2005-11-03 10:09:09 +00:00
interfaces.py more on relations - now includes an IObjectRemoved event handler and more fine-grained permissions 2005-11-05 14:13:47 +00:00
README.txt more on relations - now includes an IObjectRemoved event handler and more fine-grained permissions 2005-11-05 14:13:47 +00:00
registry.py more on relations - now includes an IObjectRemoved event handler and more fine-grained permissions 2005-11-05 14:13:47 +00:00
tests.py relation package now with a relations registry using catalog indexes 2005-11-02 13:34:27 +00:00

Yet Another Relations (Reference) Engine...
===========================================

    >>> from zope.app.testing.placelesssetup import setUp
    >>> setUp()

Let's start with two classes and a few objects that we will connect using
relations (we derive the classes from Persistent, thus we will be able to
reference these objects via IntIds later; the __parent__ and __name__
attributes are also needed later when we send an IObjectRemovedEvent event):

    >>> from persistent import Persistent
    >>> class Person(Persistent):
    ...     __name__= __parent__ = None

    >>> class City(Persistent):
    ...     pass

    >>> clark = Person()
    >>> kirk = Person()
    >>> audrey = Person()
    >>> washington = City()
    >>> newyork = City()

The relation we'll use tells us in which city a person lives; this is a dyadic
relation as it connects two objects. We also associate the relationship
with an interface as we will later use this interface for querying relations.


Dyadic Relations
~~~~~~~~~~~~~~~~

    >>> from cybertools.relation import DyadicRelation
    >>> class LivesIn(DyadicRelation):
    ...     pass

We don't directly keep track of relations but use a relations registry for
this. The relations registry is usually a local utility; for testing we use
a simple dummy implementation:

    >>> from cybertools.relation.registry import DummyRelationsRegistry
    >>> relations = DummyRelationsRegistry()

So we are ready to connect a person and a city using the LivesIn relationship:

    >>> relations.register(LivesIn(clark, washington))
    >>> relations.register(LivesIn(audrey, newyork))
    >>> relations.register(LivesIn(kirk, newyork))

We can now query the relations registry to find out where clark lives and
who lives in New York. For this we use the standard attributes of dyadic
relations, first and second:

    >>> clarkRels = relations.query(first=clark)
    >>> len(clarkRels)
    1
    >>> clarkRels[0].second == washington
    True

    >>> nyRels = relations.query(second=newyork)
    >>> len(nyRels)
    2

It is also possible to remove a relation from the relation registry:

    >>> relations.unregister(
    ...     relations.query(first=audrey, second=newyork)[0]
    ... )
    >>> nyRels = relations.query(second=newyork)
    >>> len(nyRels)
    1
    >>> nyRels[0].first == kirk
    True

    
Triadic Relations
~~~~~~~~~~~~~~~~~

We now extend our setting using a triadic relationship - triadic relations
connect three objects. (If you want to connect more than three objects you
may use combinations of triadic and dyadic relations.)

    >>> from cybertools.relation import TriadicRelation
    >>> class ParentsOf(TriadicRelation):
    ...     """ first (father) and second (mother) are the parents of
    ...         third (child)."""

    >>> relations.register(ParentsOf(clark, audrey, kirk))

When we search for relations that contain clark as first we get both:
    
    >>> clarkRels = relations.query(first=clark)
    >>> len(clarkRels)
    2

So we want to look only for ParentsOf relationships - this should give us
all relations for clark's children:

    >>> clarkChildren = relations.query(relationship=ParentsOf,
    ...                                 first=clark)
    >>> len(clarkChildren)
    1
    >>> clarkChildren[0].second == audrey
    True
    >>> clarkChildren[0].third == kirk
    True

    
Setting up and using a RelationsRegistry local utility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

We now do the same stuff as above with a real, catalog-based implementation of
the relations registry. We also register the relations registry as a
utility for demonstration purposes (and to be able to use it later when
working with events).

    >>> from cybertools.relation.registry import RelationsRegistry
    >>> from cybertools.relation.interfaces import IRelationsRegistry
    >>> from zope.app.testing import ztapi
    >>> ztapi.provideUtility(IRelationsRegistry, RelationsRegistry())    

In order to register relations the objects that are referenced have to be
registered with an IntIds (unique ids) utility, so we have to set up such
an utility (using a stub/dummy implementation for testing purposes):

    >>> from cybertools.relation.tests import IntIdsStub
    >>> from zope.app.intid.interfaces import IIntIds
    >>> ztapi.provideUtility(IIntIds, IntIdsStub())

We also have to provide an adapter for the Relation objects that provides
the attributes needed for indexing:

    >>> from cybertools.relation.registry import IIndexableRelation
    >>> from cybertools.relation.registry import IndexableRelationAdapter
    >>> from cybertools.relation.interfaces import IRelation
    >>> ztapi.provideAdapter(IRelation, IIndexableRelation,
    ...                      IndexableRelationAdapter)

So we are ready again to register a set of relations with our new relations
registry and query it.

    >>> from zope.app import zapi
    >>> relations = zapi.getUtility(IRelationsRegistry)
    
    >>> relations.register(LivesIn(clark, washington))
    >>> relations.register(LivesIn(audrey, newyork))
    >>> relations.register(LivesIn(kirk, newyork))

As we now get back a result set we have to convert the query results to a list
if we want to access relations by array index:
    
    >>> clarkRels = list(relations.query(first=clark))
    >>> len(clarkRels)
    1
    >>> clarkRels[0].second == washington
    True

    >>> nyRels = relations.query(second=newyork)
    >>> len(nyRels)
    2

    >>> relations.unregister(
    ...     list(relations.query(first=audrey, second=newyork))[0]
    ... )
    >>> nyRels = list(relations.query(second=newyork))
    >>> len(nyRels)
    1
    >>> nyRels[0].first == kirk
    True

It should work also for triadic relations:

    >>> relations.register(ParentsOf(clark, audrey, kirk))

    >>> clarkRels = relations.query(first=clark)
    >>> len(clarkRels)
    2

    >>> clarkChildren = list(relations.query(relationship=ParentsOf,
    ...                                      first=clark))
    >>> len(clarkChildren)
    1
    >>> clarkChildren[0].second == audrey
    True
    >>> clarkChildren[0].third == kirk
    True


Handling object removal
~~~~~~~~~~~~~~~~~~~~~~~

Often it is desirable to unregister a when one of the objects
involved in the relation is removed from its container. This can be
done by subscribing to IObjectRemovedEvent. The relation.registry module
provides a simple handler for this event.

    >>> from zope.app.container.interfaces import IObjectRemovedEvent
    >>> from zope.interface import Interface
    >>> from cybertools.relation.registry import unregisterRelations 
    >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None, unregisterRelations)

We simulate the removal of kirk by calling notify:

    >>> from zope.app.container.contained import ObjectRemovedEvent
    >>> from zope.event import notify
    
    >>> len(relations.query(first=clark))
    2
    
    >>> notify(ObjectRemovedEvent(kirk))

Thus there should only remain one relation containing clark as first:

    >>> len(relations.query(first=clark))
    1