diff -r 8d1ca4150397 -r 7d3044271a29 doc/book/en/devrepo/entityclasses/adapters.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/devrepo/entityclasses/adapters.rst Mon Jul 05 12:37:21 2010 +0200 @@ -0,0 +1,168 @@ +.. _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 + behviour 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.