==========
References
==========

Objects that live in the Substance D resource tree can be related to one
another using references.

The most user-visible facet of references is the SDI "References" tab, which is
presented to SDI admin users when the object they're looking at is involved in
a reference relation.  For example, you'll notice that the built-in user and
group implementations already have references to each other, and you can visit
their References tabs to see them.  Likewise, when you use the Security tab to
change the ACL associated with an object, and include in the ACL a user or
group that lives in the principals folder, a relation is formed between the
ACL-bearing object and the principal.  So, as you can see, references aren't
just for application developers; Substance D itself uses references under the
hood to do its job too.

A reference has a type and a direction.  A reference is formed using methods of
the :term:`object map`.

.. code-block:: python

   from substanced.interfaces import ReferenceType
   from substanced.objectmap import find_objectmap

   class ContextToRoot(ReferenceType):
       pass

   def connect_reference(context, request):
       objectmap = find_objectmap(context)
       root = request.root
       objectmap.connect(context, root, ContextToRoot)

A reference type is a class (not an instance) that inherits from
:class:`substanced.interfaces.ReferenceType`.  The reference's name should
indicate its directionality.

.. warning::

   One caveat: reference types are *pickled*, so if you move a reference type
   from one location to another, you'll have to leave behind a backwards
   compatibility import in its original location "forever", so choose its name
   and location wisely.  We recommend that you place it in an ``interfaces.py``
   file in your project.

A reference can be removed using the object map too:

.. code-block:: python

   from substanced.interfaces import ReferenceType
   from substanced.objectmap import find_objectmap

   class ContextToRoot(ReferenceType):
       pass

   def disconnect_reference(context, request):
       objectmap = find_objectmap(context)
       root = request.root
       objectmap.disconnect(context, root, ContextToRoot)

The first two arguments to :meth:`~substanced.objectmap.ObjectMap.connect` or
:meth:`~substanced.objectmap.ObjectMap.disconnect` are *source* and *target*.
These can be either resource objects or oids.  The third argument to these
functions is the reference type.

Once a reference is formed between two objects, you can see the reference
within the "References" tab in the SDI.  The References tab of either side of
the reference (in the above example, either the root or the context) when
visited in the SDI will display the reference to the other side.  

Once a reference is made between two objects, the object map can be queried for
objects which take part in the reference.

.. code-block:: python

   from substanced.interfaces import ReferenceType
   from substanced.objectmap import find_objectmap

   class ContextToRoot(ReferenceType):
       pass

   def query_reference_sources(context, request):
       objectmap = find_objectmap(context)
       return objectmap.sourceids(request.root, ContextToRoot)

   def query_reference_targets(context, request):
       objectmap = find_objectmap(context)
       return objectmap.targetids(context, ContextToRoot)

The :meth:`~substanced.objectmap.ObjectMap.sourceids` method returns the set of
objectids which are *sources* of the object and reference type it's passed.
The :meth:`~substanced.objectmap.ObjectMap.targetids` method returns the set of
objectids which are *targets* of the object and reference type it's passed.  If
no objects are involved in the relation, an empty set will be returned in
either case.  :meth:`~substanced.objectmap.ObjectMap.sources` and
:meth:`~substanced.objectmap.ObjectMap.targets` methods also exist which are
analogous, but return the actual objects involved in the relation instead of the
objectids:

.. code-block:: python

   from substanced.interfaces import ReferenceType
   from substanced.objectmap import find_objectmap

   class ContextToRoot(ReferenceType):
       pass

   def query_reference_sources(context, request):
       objectmap = find_objectmap(context)
       return objectmap.sources(request.root, ContextToRoot)

   def query_reference_targets(context, request):
       objectmap = find_objectmap(context)
       return objectmap.targets(context, ContextToRoot)


A reference type can claim that it is "integral", which just means that the
deletion of either the source or the target of a reference will be
prevented.  Here's an example of a "source integral" reference type:

.. code-block:: python

   from substanced.interfaces import ReferenceType

   class UserToGroup(ReferenceType):
       source_integrity = True

This reference type will prevent any object on the "user" side of the
UserToGroup reference (as opposed to the group side) from being deleted.  When
a user attempts to delete a user that's related to a group using this reference
type, a :class:`substanced.objectmap.SourceIntegrityError` will be raised and
the deletion will be prevented.  Only when the reference is removed or the
group is deleted will the user deletion be permitted.

The flip side of this is target integrity:

.. code-block:: python

   from substanced.interfaces import ReferenceType

   class UserToGroup(ReferenceType):
       target_integrity = True

This is the inverse.  The reference will prevent any object on the "group" side
of the UserToGroup reference from being deleted unless the associated user is
first removed or the reference itself is no longer active.  When a user
attempts to delete a user that's related to a group using this reference type,
a :class:`substanced.objectmap.TargetIntegrityError` will be raised and the
deletion will be prevented.

:class:`substanced.objectmap.SourceIntegrityError` and
:class:`substanced.objectmap.TargetIntegrityError` both inherit from
:class:`substanced.objectmap.ReferentialIntegrityError`, so you can catch
either in your code.

There are convenience functions that you can add to your resource objects that
give them special behavior:
:func:`~substanced.objectmap.reference_sourceid_property`,
:func:`~substanced.objectmap.reference_targetid_property`,
:func:`~substanced.objectmap.reference_source_property`,
:func:`~substanced.objectmap.reference_target_property`,
:func:`~substanced.objectmap.multireference_sourceid_property`,
:func:`~substanced.objectmap.multireference_targetid_property`,
:func:`~substanced.objectmap.reference_source_property`, and
:func:`~substanced.objectmap.reference_target_property`.

Here's use of a reference property:

.. code-block:: python
   :linenos:

   from persistent import Persistent
   from substanced.objectmap import reference_sourceid_property
   from substanced.interfaces import ReferenceType

   class LineItemToOrder(ReferenceType):
       pass

   class LineItem(Persistent):
       order = reference_target_property(LineItemToOrder)

Once you've seated a resource object in a folder, you can then begin to use its
special properties:

.. code-block:: python
   :linenos:

   from mysystem import LineItem, Order

   lineitem = LineItem()
   folder['lineitem'] = lineitem
   lineitem.order = Order()

This is just a nicer way to use the objectmap query API; you don't have to
interact with it at all, just assign and ask for attributes of your object.
The ``multireference_*`` variants are similar to the reference variants, but
they allow for more than one object on the "other side".

ACLs and Principal References
=============================

When an ACL is modified on a resource, a statement is being made about
a relationship between that resource and a principal or group of
principals. Wouldn't it be great if a reference was established,
allowing you to then see such connections in the SDI?

This is indeed exactly how Substance D behaves: a source-integral
PrincipalToACLBearing reference is set up between an ACL-bearing
resource and the principals referred to within the ACL.
