===================
Referencing objects
===================


Simple references
=================

For working with references you have to have a located site set:

>>> import zope.app.component.hooks
>>> root = getRootFolder()
>>> zope.app.component.hooks.setSite(root)

>>> from gocept.reference.tests import Address, City

>>> root['dessau'] = City('Dessau', '06844')
>>> root['halle'] = City('Halle', '06110')
>>> root['jena'] = City('Jena', '07743')

>>> theuni = Address('Christian Theune', root['dessau'])

>>> theuni.city
<gocept.reference.tests.City object at 0x...>

>>> theuni.city = None
>>> print theuni.city
None

Values can be deleted, the property raises an AttributeError then:

>>> del theuni.city
>>> theuni.city
Traceback (most recent call last):
AttributeError: city

Only contained objects can be assigned to a reference property that has
integrity ensurance enabled:

>>> theuni.city = 12
Traceback (most recent call last):
TypeError: ...


Integrity-ensured references
============================

>>> from gocept.reference.tests import Monument

Located source
--------------

Referential integrity can be ensured iff the source of the reference is
located:

>>> root['fuchsturm'] = Monument('Fuchsturm', root['dessau'])
>>> root['fuchsturm'].city is root['dessau']
True

>>> import transaction
>>> transaction.commit()

>>> del root['dessau']
Traceback (most recent call last):
IntegrityError: Can't delete or move
  <gocept.reference.tests.City object at 0x...>.
  The (sub-)object <gocept.reference.tests.City object at 0x...> is
  still being referenced.

>>> transaction.commit()
Traceback (most recent call last):
DoomedTransaction

>>> transaction.abort()
>>> 'dessau' in root
True

>>> from gocept.reference.interfaces import IReferenceTarget

>>> IReferenceTarget(root['dessau']).is_referenced()
True

>>> root['fuchsturm'].city = None
>>> IReferenceTarget(root['dessau']).is_referenced()
False

>>> del root['dessau']
>>> 'dessau' in root
False

XXX References will also be correctly cancelled when the attribute or the
source is deleted.

>>> del root['fuchsturm']

Non-located source
------------------

If the source of a reference is not located, we can do anything we want with
references, including breaking them:

>>> fuchsturm = Monument('Fuchsturm', root['jena'])
>>> fuchsturm.city is root['jena']
True

>>> del fuchsturm.city
>>> fuchsturm.city
Traceback (most recent call last):
AttributeError: city

>>> fuchsturm.city = root['jena']
>>> fuchsturm.city is root['jena']
True

>>> del root['jena']
>>> fuchsturm.city
Traceback (most recent call last):
LookupError: Target u'/jena' of reference 'city' no longer exists.


Changing the location state of the source
-----------------------------------------

We cannot put an object with a broken reference back into containment since
referential integrity is not given:

>>> transaction.commit()

>>> root['fuchsturm'] = fuchsturm
Traceback (most recent call last):
LookupError: Target u'/jena' of reference 'city' no longer exists.

The transaction was doomed, let's recover the last working state:

>>> transaction.commit()
Traceback (most recent call last):
DoomedTransaction

>>> transaction.abort()

We have to repair the fuchsturm object by hand as it was not part of the
transaction:

>>> fuchsturm.__parent__ = fuchsturm.__name__ = None

>>> from gocept.reference.interfaces import IReferenceSource
>>> IReferenceSource(fuchsturm).verify_integrity()
False

>>> IReferenceTarget(root['halle']).is_referenced()
False
>>> fuchsturm.city = root['halle']
>>> IReferenceSource(fuchsturm).verify_integrity()
True
>>> IReferenceTarget(root['halle']).is_referenced()
False

>>> root['fuchsturm'] = fuchsturm
>>> IReferenceTarget(root['halle']).is_referenced()
True

>>> fuchsturm = root['fuchsturm']
>>> del root['fuchsturm']
>>> fuchsturm.city is root['halle']
True

>>> del root['halle']
>>> 'halle' in root
False

Hierarchіcal structures
-----------------------

Trying to delete objects that contain referenced objects with ensured
integrity is also forbidden:

>>> import zope.app.container.sample
>>> root['folder'] = zope.app.container.sample.SampleContainer()
>>> root['folder']['frankfurt'] = City('Frankfurt', '12345')
>>> root['messeturm'] = Monument('Messeturm', root['folder']['frankfurt'])

Deleting the `folder` will fail now, because a subobject is being referenced.
The reference target API (IReferenceTarget) allows us to inspect it
beforehand:

>>> from gocept.reference.interfaces import IReferenceTarget
>>> folder_target = IReferenceTarget(root['folder'])
>>> folder_target.is_referenced()
True
>>> folder_target.is_referenced(recursive=False)
False


>>> del root['folder']
Traceback (most recent call last):
IntegrityError: Can't delete or move
  <zope.app.container.sample.SampleContainer object at 0x...>.
  The (sub-)object <gocept.reference.tests.City object at 0x...> is still
  being referenced.


Upgrading from unconstrained to constrained references
------------------------------------------------------

XXX

Downgrading from integrity ensured references to unensured
----------------------------------------------------------

XXX
