.. _adapters:
Interfaces and Adapters
-----------------------
Interfaces are the same thing as object-oriented programming
`interfaces`_. Adapter refers to a well-known `adapter`_ design
pattern that helps separating concerns in object oriented
applications.
.. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
.. _`adapter`: http://en.wikipedia.org/wiki/Adapter_pattern
In |cubicweb| adapters provide logical functionalities
to entity types. They are introduced in version `3.9`. Before that one
had to implements Interfaces in entity classes to achieve a similar goal. However,
hte problem with this approch is that is clutters the entity class's namespace, exposing
name collision risks with schema attributes/relations or even methods names
(different interfaces may define the same method with not necessarily the same
behaviour expected).
Definition of an adapter is quite trivial. An excerpt from cubicweb
itself (found in :mod:`cubicweb.entities.adapters`):
.. sourcecode:: python
class ITreeAdapter(EntityAdapter):
"""This adapter has to be overriden to be configured using the
tree_relation, child_role and parent_role class attributes to
benefit from this default implementation
"""
__regid__ = 'ITree'
child_role = 'subject'
parent_role = 'object'
def children_rql(self):
"""returns RQL to get children """
return self.entity.cw_related_rql(self.tree_relation, self.parent_role)
The adapter object has ``self.entity`` attribute which represents the
entity being adapted.
Specializing and binding an adapter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. sourcecode:: python
from cubicweb.entities.adapters import ITreeAdapter
class MyEntityITreeAdapter(ITreeAdapter):
__select__ = implements('MyEntity')
tree_relation = 'filed_under'
The ITreeAdapter here provides a default implementation. The
tree_relation class attribute is actually used by this implementation
to help implement correct behaviour.
Here we provide a specific implementation which will be bound for
``MyEntity`` entity type (the `adaptee`).
Selecting on an adapter
~~~~~~~~~~~~~~~~~~~~~~~
There is an ``adaptable`` selector which can be used instead of
``implements``.
.. _interfaces_to_adapters:
Converting code from Interfaces/Mixins to Adapters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here we go with a small example. Before:
.. sourcecode:: python
from cubicweb.selectors import implements
from cubicweb.interfaces import ITree
from cubicweb.mixins import ITreeMixIn
class MyEntity(ITreeMixIn, AnyEntity):
__implements__ = AnyEntity.__implements__ + (ITree,)
class ITreeView(EntityView):
__select__ = implements('ITree')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
children = entity.children()
After:
.. sourcecode:: python
from cubicweb.selectors import adaptable, implements
from cubicweb.entities.adapters import ITreeAdapter
class MyEntityITreeAdapter(ITreeAdapter):
__select__ = implements('MyEntity')
class ITreeView(EntityView):
__select__ = adaptable('ITree')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
itree = entity.cw_adapt_to('ITree')
children = itree.children()
As we can see, the interface/mixin duality disappears and the entity
class itself is completely freed from these concerns. When you want
to use the ITree interface of an entity, call its `cw_adapt_to` method
to get an adapter for this interface, then access to members of the
interface on the adapter
Let's look at an example where we defined everything ourselves. We
start from:
.. sourcecode:: python
class IFoo(Interface):
def bar(self, *args):
raise NotImplementedError
class MyEntity(AnyEntity):
__regid__ = 'MyEntity'
def bar(self, *args):
return sum(captain.age for captain in self.captains)
class FooView(EntityView):
__regid__ = 'mycube.fooview'
__select__ = implements('IFoo')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
self.w('bar: %s' % entity.bar())
Converting to:
.. sourcecode:: python
class IFooAdapter(EntityAdapter):
__regid__ = 'IFoo'
def bar(self, *args):
return sum(captain.age for captain in self.entity.captains)
class FooView(EntityView):
__regid__ = 'mycube.fooview'
__select__ = adaptable('IFoo')
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
self.w('bar: %s' % entity.cw_adapt_to('IFoo').bar())
.. note::
When migrating an entity method to an adapter, the code can be moved as is
except for the `self` of the entity class, which in the adapter must become `self.entity`.
Adapters defined in the library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: cubicweb.entities.adapters
:members:
More are defined in web/views.