doc/book/en/devrepo/entityclasses/adapters.rst
changeset 10491 c67bcee93248
parent 10490 76ab3c71aff2
child 10492 68c13e0c0fc5
equal deleted inserted replaced
10490:76ab3c71aff2 10491:c67bcee93248
     1 .. _adapters:
       
     2 
       
     3 Interfaces and Adapters
       
     4 -----------------------
       
     5 
       
     6 Interfaces are the same thing as object-oriented programming `interfaces`_.
       
     7 Adapter refers to a well-known `adapter`_ design pattern that helps separating
       
     8 concerns in object oriented applications.
       
     9 
       
    10 .. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
       
    11 .. _`adapter`: http://en.wikipedia.org/wiki/Adapter_pattern
       
    12 
       
    13 In |cubicweb| adapters provide logical functionalities to entity types.
       
    14 
       
    15 Definition of an adapter is quite trivial. An excerpt from cubicweb
       
    16 itself (found in :mod:`cubicweb.entities.adapters`):
       
    17 
       
    18 .. sourcecode:: python
       
    19 
       
    20 
       
    21     class ITreeAdapter(EntityAdapter):
       
    22         """This adapter has to be overriden to be configured using the
       
    23         tree_relation, child_role and parent_role class attributes to
       
    24         benefit from this default implementation
       
    25         """
       
    26         __regid__ = 'ITree'
       
    27 
       
    28         child_role = 'subject'
       
    29         parent_role = 'object'
       
    30 
       
    31         def children_rql(self):
       
    32             """returns RQL to get children """
       
    33             return self.entity.cw_related_rql(self.tree_relation, self.parent_role)
       
    34 
       
    35 The adapter object has ``self.entity`` attribute which represents the
       
    36 entity being adapted.
       
    37 
       
    38 .. Note::
       
    39 
       
    40    Adapters came with the notion of service identified by the registry identifier
       
    41    of an adapters, hence dropping the need for explicit interface and the
       
    42    :class:`cubicweb.predicates.implements` selector. You should instead use
       
    43    :class:`cubicweb.predicates.is_instance` when you want to select on an entity
       
    44    type, or :class:`cubicweb.predicates.adaptable` when you want to select on a
       
    45    service.
       
    46 
       
    47 
       
    48 Specializing and binding an adapter
       
    49 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    50 
       
    51 .. sourcecode:: python
       
    52 
       
    53   from cubicweb.entities.adapters import ITreeAdapter
       
    54 
       
    55   class MyEntityITreeAdapter(ITreeAdapter):
       
    56       __select__ = is_instance('MyEntity')
       
    57       tree_relation = 'filed_under'
       
    58 
       
    59 The ITreeAdapter here provides a default implementation. The
       
    60 tree_relation class attribute is actually used by this implementation
       
    61 to help implement correct behaviour.
       
    62 
       
    63 Here we provide a specific implementation which will be bound for
       
    64 ``MyEntity`` entity type (the `adaptee`).
       
    65 
       
    66 
       
    67 .. _interfaces_to_adapters:
       
    68 
       
    69 Converting code from Interfaces/Mixins to Adapters
       
    70 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    71 
       
    72 Here we go with a small example. Before:
       
    73 
       
    74 .. sourcecode:: python
       
    75 
       
    76     from cubicweb.predicates import implements
       
    77     from cubicweb.interfaces import ITree
       
    78     from cubicweb.mixins import ITreeMixIn
       
    79 
       
    80     class MyEntity(ITreeMixIn, AnyEntity):
       
    81         __implements__ = AnyEntity.__implements__ + (ITree,)
       
    82 
       
    83 
       
    84     class ITreeView(EntityView):
       
    85         __select__ = implements('ITree')
       
    86         def cell_call(self, row, col):
       
    87             entity = self.cw_rset.get_entity(row, col)
       
    88             children = entity.children()
       
    89 
       
    90 After:
       
    91 
       
    92 .. sourcecode:: python
       
    93 
       
    94     from cubicweb.predicates import adaptable, is_instance
       
    95     from cubicweb.entities.adapters import ITreeAdapter
       
    96 
       
    97     class MyEntityITreeAdapter(ITreeAdapter):
       
    98         __select__ = is_instance('MyEntity')
       
    99 
       
   100     class ITreeView(EntityView):
       
   101         __select__ = adaptable('ITree')
       
   102         def cell_call(self, row, col):
       
   103             entity = self.cw_rset.get_entity(row, col)
       
   104             itree = entity.cw_adapt_to('ITree')
       
   105             children = itree.children()
       
   106 
       
   107 As we can see, the interface/mixin duality disappears and the entity
       
   108 class itself is completely freed from these concerns. When you want
       
   109 to use the ITree interface of an entity, call its `cw_adapt_to` method
       
   110 to get an adapter for this interface, then access to members of the
       
   111 interface on the adapter
       
   112 
       
   113 Let's look at an example where we defined everything ourselves. We
       
   114 start from:
       
   115 
       
   116 .. sourcecode:: python
       
   117 
       
   118     class IFoo(Interface):
       
   119         def bar(self, *args):
       
   120             raise NotImplementedError
       
   121 
       
   122     class MyEntity(AnyEntity):
       
   123         __regid__ = 'MyEntity'
       
   124         __implements__ = AnyEntity.__implements__ + (IFoo,)
       
   125 
       
   126         def bar(self, *args):
       
   127             return sum(captain.age for captain in self.captains)
       
   128 
       
   129     class FooView(EntityView):
       
   130         __regid__ = 'mycube.fooview'
       
   131         __select__ = implements('IFoo')
       
   132 
       
   133         def cell_call(self, row, col):
       
   134             entity = self.cw_rset.get_entity(row, col)
       
   135             self.w('bar: %s' % entity.bar())
       
   136 
       
   137 Converting to:
       
   138 
       
   139 .. sourcecode:: python
       
   140 
       
   141    class IFooAdapter(EntityAdapter):
       
   142        __regid__ = 'IFoo'
       
   143        __select__ = is_instance('MyEntity')
       
   144 
       
   145        def bar(self, *args):
       
   146            return sum(captain.age for captain in self.entity.captains)
       
   147 
       
   148    class FooView(EntityView):
       
   149         __regid__ = 'mycube.fooview'
       
   150         __select__ = adaptable('IFoo')
       
   151 
       
   152         def cell_call(self, row, col):
       
   153             entity = self.cw_rset.get_entity(row, col)
       
   154             self.w('bar: %s' % entity.cw_adapt_to('IFoo').bar())
       
   155 
       
   156 .. note::
       
   157 
       
   158    When migrating an entity method to an adapter, the code can be moved as is
       
   159    except for the `self` of the entity class, which in the adapter must become `self.entity`.
       
   160 
       
   161 Adapters defined in the library
       
   162 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   163 
       
   164 .. automodule:: cubicweb.entities.adapters
       
   165    :members:
       
   166 
       
   167 More are defined in web/views.