How to use entities objects
---------------------------
The previous chapters detailed the classes and methods available to
the developper at the so-called `ORM`_ level. However they say little
about the common patterns of usage of these objects.
.. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
Entities objects are used in the repository and web sides of
CubicWeb. In 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
same role as database triggers, but in a way that is independant of
the actual data sources).
So a lot of an application's business rules will be written in Hooks
(or Operations).
In the web side, views also typically operate using entity
objects. Obvious entity methods for use in views are the dublin code
method like dc_title, etc. For separation of concerns reasons, one
should ensure no ui logic pervades the entities level, and also no
business logic should creep into the views.
In the duration of a transaction, entities objects can be instantiated
many times, in views and hooks, even for the same database entity. For
instance, in a classic CubicWeb deployment setup, the repository and
the web frontend are separated process communicating over the
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`,
whether or not x is actually an entity schema attribute, has a short
life span, limited to the hook, operation or view within the object
was built.
Setting an attribute value should always be done in the context of a
Hook/Operation, using the obj.set_attributes(x=42) notation or a plain
RQL SET expression.
That still leaves for entity objects an essential role: it's where an
important part of the application logic lie (the other part being
located in the Hook/Operations).
Anatomy of an entity class
--------------------------
We can look now at a real life example coming from the `tracker`_
cube. Let us begin to study the entities/project.py content.
.. sourcecode:: python
class Project(TreeMixIn, 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). It is important
that the views themselves try not to implement this logic, not only
because such views would be hardly applyable to other tree-like
relations, but also because it is perfectly fine and useful to use
such an interface in Hooks. In fact, Tree nature is a property of the
data model that cannot be fully and portably expressed at the level of
database entities (think about the transitive closure of the child
relation).
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
responsible of ensuring XHTML compliance), or a console or file
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
when SORTing in an RQL expression concerned with many such entities.
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.
Let us now dig into more substantial pieces of code.
.. sourcecode:: python
def latest_version(self, states=('published',), reverse=None):
"""returns the latest version(s) for the project in one of the given
states.
when no states specified, returns the latest published version.
"""
order = 'DESC'
if reverse is not None:
warn('reverse argument is deprecated',
DeprecationWarning, stacklevel=1)
if reverse:
order = 'ASC'
rset = self.versions_in_state(states, order, True)
if rset:
return rset.get_entity(0, 0)
return None
def versions_in_state(self, states, order='ASC', limit=False):
"""returns version(s) for the project in one of the given states, sorted
by version number.
If limit is true, limit result to one version.
If reverse, versions are returned from the smallest to the greatest.
"""
if limit:
order += ' LIMIT 1'
rql = 'Any V,N ORDERBY version_sort_value(N) %s ' \
'WHERE V num N, V in_state S, S name IN (%s), ' \
'V version_of P, P eid %%(p)s' % (order, ','.join(repr(s) for s in states))
return self._cw.execute(rql, {'p': self.eid})
.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker/
These few lines exhibit the important properties we want to outline:
* entity code is concerned with the application domain
* 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
* however it can be used in both contexts
* it does not create or manipulate the internal object's state
* it plays freely with RQL expression as needed
* it is not concerned with internationalization
* it does not raise exceptions