--- /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.
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst Mon Jul 05 12:37:13 2010 +0200
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Mon Jul 05 12:37:21 2010 +0200
@@ -1,5 +1,5 @@
-How to use entities objects
----------------------------
+How to use entities objects and adapters
+----------------------------------------
The previous chapters detailed the classes and methods available to
the developper at the so-called `ORM`_ level. However they say little
@@ -7,9 +7,9 @@
.. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
-Entities objects are used in the repository and web sides of
-CubicWeb. On the repository side of things, one should manipulate them
-in Hooks and Operations.
+Entities objects (and their adapters) are used in the repository and
+web sides of CubicWeb. On the repository side of things, one should
+manipulate them in Hooks and Operations.
Hooks and Operations provide support for the implementation of rules
such as computed attributes, coherency invariants, etc (they play the
@@ -32,21 +32,22 @@
wire. There is no way state can be shared between these processes
(there is a specific API for that). Hence, it is not possible to use
entity objects as messengers between these components of an
-application. It means that an attribute set as in `obj.x = 42`,
+application. It means that an attribute set as in ``obj.x = 42``,
whether or not x is actually an entity schema attribute, has a short
life span, limited to the hook, operation or view within which the
object was built.
Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_attributes(x=42) notation or a plain
+Hook/Operation, using the obj.set_relations(x=42) notation or a plain
RQL SET expression.
In views, it would be preferable to encapsulate the necessary logic in
-a method of the concerned entity class(es). But of course, this advice
-is also reasonnable for Hooks/Operations, though the separation of
-concerns here is less stringent than in the case of views.
+a method of an adapter for the concerned entity class(es). But of
+course, this advice is also reasonnable for Hooks/Operations, though
+the separation of concerns here is less stringent than in the case of
+views.
-This leads to the practical role of entity objects: it's where an
+This leads to the practical role of objects adapters: it's where an
important part of the application logic lie (the other part being
located in the Hook/Operations).
@@ -58,26 +59,31 @@
.. sourcecode:: python
- class Project(TreeMixIn, AnyEntity):
+ from cubicweb.entities.adapters import ITreeAdapter
+
+ class ProjectAdapter(ITreeAdapter):
+ __select__ = implements('Project')
+ tree_relation = 'subproject_of'
+
+ class Project(AnyEntity):
__regid__ = 'Project'
- __implements__ = AnyEntity.__implements__ + (ITree,)
fetch_attrs, fetch_order = fetch_config(('name', 'description',
'description_format', 'summary'))
TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")'
- tree_attribute = 'subproject_of'
- parent_target = 'subject'
- children_target = 'object'
-
def dc_title(self):
return self.name
-First we see that it uses an ITree interface and the TreeMixIn default
-implementation. The attributes `tree_attribute`, `parent_target` and
-`children_target` are used by the TreeMixIn code. This is typically
-used in views concerned with the representation of tree-like
-structures (CubicWeb provides several such views).
+The fact that the `Project` entity type implements an ``ITree``
+interface is materialized by the ``ProjectAdapter`` class (inheriting
+the pre-defined ``ITreeAdapter`` whose __regid__ is of course
+``ITree``), which will be selected on `Project` entity types because
+of its selector. On this adapter, we redefine the ``tree_relation``
+attribute of the ITreeAdapter class.
+
+This is typically used in views concerned with the representation of
+tree-like structures (CubicWeb provides several such views).
It is important that the views themselves try not to implement this
logic, not only because such views would be hardly applyable to other
@@ -89,7 +95,17 @@
about the transitive closure of the child relation). This is a further
argument to implement it at entity class level.
-The `dc_title` method provides a (unicode string) value likely to be
+The fetch_attrs, fetch_order class attributes are parameters of the
+`ORM`_ layer. They tell which attributes should be loaded at once on
+entity object instantiation (by default, only the eid is known, other
+attributes are loaded on demand), and which attribute is to be used to
+order the .related() and .unrelated() methods output.
+
+We can observe the big TICKET_DEFAULT_STATE_RESTR is a pure
+application domain piece of data. There is, of course, no limitation
+to the amount of class attributes of this kind.
+
+The ``dc_title`` method provides a (unicode string) value likely to be
consummed by views, but note that here we do not care about output
encodings. We care about providing data in the most universal format
possible, because the data could be used by a web view (which would be
@@ -97,17 +113,14 @@
oriented output (which would have the necessary context about the
needed byte stream encoding).
-The fetch_attrs, fetch_order class attributes are parameters of the
-`ORM`_ layer. They tell which attributes should be loaded at once on
-entity object instantiation (by default, only the eid is known, other
-attributes are loaded on demand), and which attribute is to be used to
-order the .related() and .unrelated() methods output.
+.. note::
-Finally, we can observe the big TICKET_DEFAULT_STATE_RESTR is a pure
-application domain piece of data. There is, of course, no limitation
-to the amount of class attributes of this kind.
+ The dublin code `dc_xxx` methods are not moved to an adapter as they
+ are extremely prevalent in cubicweb and assorted cubes and should be
+ available for all entity types.
-Let us now dig into more substantial pieces of code.
+Let us now dig into more substantial pieces of code, continuing the
+Project class.
.. sourcecode:: python
@@ -151,7 +164,7 @@
* it is NOT concerned with database coherency (this is the realm of
Hooks/Operations); in other words, it assumes a coherent world
-* it is NOT concerned with end-user interfaces
+* it is NOT (directly) concerned with end-user interfaces
* however it can be used in both contexts
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst Mon Jul 05 12:37:13 2010 +0200
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst Mon Jul 05 12:37:21 2010 +0200
@@ -66,7 +66,7 @@
The :class:`AnyEntity` class
----------------------------
-To provide a specific behavior for each entity, we have to define a class
+To provide a specific behavior for each entity, we can define a class
inheriting from `cubicweb.entities.AnyEntity`. In general, we define this class
in `mycube.entities` module (or in a submodule if we want to split code among
multiple files) so that it will be available on both server and client side.
@@ -139,5 +139,18 @@
one in OTHER_CUBE. These types are stored in the `etype` section of
the `vregistry`.
-Notice this is different than yams schema inheritance.
+Notice this is different than yams schema inheritance, which is an
+experimental undocumented feature.
+
+
+Application logic
+-----------------
+While a lot of custom behaviour and application logic can be
+implemented using entity classes, the programmer must be aware that
+adding new attributes and method on an entity class adds may shadow
+schema-level attribute or relation definitions.
+
+To keep entities clean (mostly data structures plus a few universal
+methods such as listed above), one should use `adapters` (see
+:ref:`adapters`).
--- a/doc/book/en/devrepo/entityclasses/index.rst Mon Jul 05 12:37:13 2010 +0200
+++ b/doc/book/en/devrepo/entityclasses/index.rst Mon Jul 05 12:37:21 2010 +0200
@@ -9,5 +9,5 @@
data-as-objects
load-sort
- interfaces
+ adapters
application-logic
--- a/doc/book/en/devrepo/entityclasses/interfaces.rst Mon Jul 05 12:37:13 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-Interfaces
-----------
-
-This is the same thing as object-oriented programming `interfaces`_.
-
-.. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
-
-Definition of an interface is quite trivial. An example from cubicweb
-itself (found in cubicweb/interfaces.py):
-
-.. sourcecode:: python
-
- class ITree(Interface):
-
- def parent(self):
- """returns the parent entity"""
-
- def children(self):
- """returns the item's children"""
-
- def children_rql(self):
- """returns RQL to get children"""
-
- def iterchildren(self):
- """iterates over the item's children"""
-
- def is_leaf(self):
- """returns true if this node as no child"""
-
- def is_root(self):
- """returns true if this node has no parent"""
-
- def root(self):
- """returns the root object"""
-
-
-Declaration of interfaces implemented by a class
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. sourcecode:: python
-
- from cubicweb.interfaces import ITree
- from cubicweb.mixins import TreeMixIn
-
- class MyEntity(TreeMixIn, AnyEntity):
- __regid__ = 'MyEntity'
- __implements__ = AnyEntity.__implements__ + ('ITree',)
-
- tree_attribute = 'filed_under'
-
-The TreeMixIn here provides a default implementation for the
-interface. The tree_attribute class attribute is actually used by this
-implementation to help implement correct behaviour.
-
-Interfaces (and some implementations as mixins) defined in the library
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. automodule:: cubicweb.interfaces
- :members:
-
-.. automodule:: cubicweb.mixins
- :members:
-
-
-
--- a/doc/book/en/devrepo/vreg.rst Mon Jul 05 12:37:13 2010 +0200
+++ b/doc/book/en/devrepo/vreg.rst Mon Jul 05 12:37:21 2010 +0200
@@ -67,7 +67,7 @@
match or not according to entity's (instance or class) properties.
.. autoclass:: cubicweb.selectors.non_final_entity
-.. autoclass:: cubicweb.selectors.implements
+.. autoclass:: cubicweb.selectors.is_instance
.. autoclass:: cubicweb.selectors.score_entity
.. autoclass:: cubicweb.selectors.rql_condition
.. autoclass:: cubicweb.selectors.relation_possible
@@ -77,6 +77,7 @@
.. autoclass:: cubicweb.selectors.has_permission
.. autoclass:: cubicweb.selectors.has_add_permission
.. autoclass:: cubicweb.selectors.has_mimetype
+.. autoclass:: cubicweb.selectors.implements
Logged user selectors
--- a/doc/book/en/devweb/views/breadcrumbs.rst Mon Jul 05 12:37:13 2010 +0200
+++ b/doc/book/en/devweb/views/breadcrumbs.rst Mon Jul 05 12:37:21 2010 +0200
@@ -8,11 +8,11 @@
~~~~~~~
Breadcrumbs are displayed by default in the header section (see
-:ref:`the_main_template_sections`). With the default main
-template, the header section is composed by the logo, the application
-name, breadcrumbs and, at the most right, the login box. Breadcrumbs
-are displayed just next to the application name, thus breadcrumbs
-begin with a separator.
+:ref:`the_main_template_sections`). With the default main template,
+the header section is composed by the logo, the application name,
+breadcrumbs and, at the most right, the login box. Breadcrumbs are
+displayed just next to the application name, thus they begin with a
+separator.
Here is the header section of the CubicWeb's forge:
@@ -22,29 +22,31 @@
:mod:`cubicweb.web.views.ibreadcrumbs`:
- `BreadCrumbEntityVComponent`: displayed for a result set with one line
- if the entity implements the ``IBreadCrumbs`` interface.
+ if the entity is adaptable to ``IBreadCrumbsAdapter``.
- `BreadCrumbETypeVComponent`: displayed for a result set with more than
- one line, but with all entities of the same type which implement the
- ``IBreadCrumbs`` interface.
+ one line, but with all entities of the same type which can adapt to
+ ``IBreadCrumbsAdapter``.
- `BreadCrumbAnyRSetVComponent`: displayed for any other result set.
Building breadcrumbs
~~~~~~~~~~~~~~~~~~~~
-The ``IBreadCrumbs`` interface is defined in the
-:mod:`cubicweb.interfaces` module. It specifies that an entity which
-implements this interface must have a ``breadcrumbs`` method.
+The ``IBreadCrumbsAdapter`` adapter is defined in the
+:mod:`cubicweb.web.views.ibreadcrumbs` module. It specifies that an
+entity which implements this interface must have a ``breadcrumbs`` and
+a ``parent_entity`` method. A default implementation for each is
+provided. This implementation expoits the ITreeAdapter.
.. note::
Redefining the breadcrumbs is the hammer way to do it. Another way
- is to define the `parent` method on an entity (as defined in the
- `ITree` interface). If available, it will be used to compute
- breadcrumbs.
+ is to define an `ITreeAdapter` adapter on an entity type. If
+ available, it will be used to compute breadcrumbs.
-Here is the API of the ``breadcrumbs`` method:
+Here is the API of the ``IBreadCrumbsAdapter`` class:
-.. automethod:: cubicweb.interfaces.IBreadCrumbs.breadcrumbs
+.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbs.parent_entity
+.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbs.breadcrumbs
If the breadcrumbs method return a list of entities, the
``cubicweb.web.views.ibreadcrumbs.BreadCrumbView`` is used to display