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