doc/book/en/devrepo/entityclasses/application-logic.rst
branchstable
changeset 5394 105011657405
parent 5157 1202e6565aff
child 5879 7d3044271a29
equal deleted inserted replaced
5393:875bdc0fe8ce 5394:105011657405
       
     1 How to use entities objects
       
     2 ---------------------------
       
     3 
       
     4 The previous chapters detailed the classes and methods available to
       
     5 the developper at the so-called `ORM`_ level. However they say little
       
     6 about the common patterns of usage of these objects.
       
     7 
       
     8 .. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
       
     9 
       
    10 Entities objects are used in the repository and web sides of
       
    11 CubicWeb. On the repository side of things, one should manipulate them
       
    12 in Hooks and Operations.
       
    13 
       
    14 Hooks and Operations provide support for the implementation of rules
       
    15 such as computed attributes, coherency invariants, etc (they play the
       
    16 same role as database triggers, but in a way that is independant of
       
    17 the actual data sources).
       
    18 
       
    19 So a lot of an application's business rules will be written in Hooks
       
    20 (or Operations).
       
    21 
       
    22 On the web side, views also typically operate using entity
       
    23 objects. Obvious entity methods for use in views are the dublin code
       
    24 method like dc_title, etc. For separation of concerns reasons, one
       
    25 should ensure no ui logic pervades the entities level, and also no
       
    26 business logic should creep into the views.
       
    27 
       
    28 In the duration of a transaction, entities objects can be instantiated
       
    29 many times, in views and hooks, even for the same database entity. For
       
    30 instance, in a classic CubicWeb deployment setup, the repository and
       
    31 the web frontend are separated process communicating over the
       
    32 wire. There is no way state can be shared between these processes
       
    33 (there is a specific API for that). Hence, it is not possible to use
       
    34 entity objects as messengers between these components of an
       
    35 application. It means that an attribute set as in `obj.x = 42`,
       
    36 whether or not x is actually an entity schema attribute, has a short
       
    37 life span, limited to the hook, operation or view within which the
       
    38 object was built.
       
    39 
       
    40 Setting an attribute or relation value can be done in the context of a
       
    41 Hook/Operation, using the obj.set_attributes(x=42) notation or a plain
       
    42 RQL SET expression.
       
    43 
       
    44 In views, it would be preferable to encapsulate the necessary logic in
       
    45 a method of the concerned entity class(es). But of course, this advice
       
    46 is also reasonnable for Hooks/Operations, though the separation of
       
    47 concerns here is less stringent than in the case of views.
       
    48 
       
    49 This leads to the practical role of entity objects: it's where an
       
    50 important part of the application logic lie (the other part being
       
    51 located in the Hook/Operations).
       
    52 
       
    53 Anatomy of an entity class
       
    54 --------------------------
       
    55 
       
    56 We can look now at a real life example coming from the `tracker`_
       
    57 cube. Let us begin to study the entities/project.py content.
       
    58 
       
    59 .. sourcecode:: python
       
    60 
       
    61     class Project(TreeMixIn, AnyEntity):
       
    62         __regid__ = 'Project'
       
    63         __implements__ = AnyEntity.__implements__ + (ITree,)
       
    64         fetch_attrs, fetch_order = fetch_config(('name', 'description',
       
    65                                                  'description_format', 'summary'))
       
    66 
       
    67         TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")'
       
    68 
       
    69         tree_attribute = 'subproject_of'
       
    70         parent_target = 'subject'
       
    71         children_target = 'object'
       
    72 
       
    73         def dc_title(self):
       
    74             return self.name
       
    75 
       
    76 First we see that it uses an ITree interface and the TreeMixIn default
       
    77 implementation. The attributes `tree_attribute`, `parent_target` and
       
    78 `children_target` are used by the TreeMixIn code. This is typically
       
    79 used in views concerned with the representation of tree-like
       
    80 structures (CubicWeb provides several such views).
       
    81 
       
    82 It is important that the views themselves try not to implement this
       
    83 logic, not only because such views would be hardly applyable to other
       
    84 tree-like relations, but also because it is perfectly fine and useful
       
    85 to use such an interface in Hooks.
       
    86 
       
    87 In fact, Tree nature is a property of the data model that cannot be
       
    88 fully and portably expressed at the level of database entities (think
       
    89 about the transitive closure of the child relation). This is a further
       
    90 argument to implement it at entity class level.
       
    91 
       
    92 The `dc_title` method provides a (unicode string) value likely to be
       
    93 consummed by views, but note that here we do not care about output
       
    94 encodings. We care about providing data in the most universal format
       
    95 possible, because the data could be used by a web view (which would be
       
    96 responsible of ensuring XHTML compliance), or a console or file
       
    97 oriented output (which would have the necessary context about the
       
    98 needed byte stream encoding).
       
    99 
       
   100 The fetch_attrs, fetch_order class attributes are parameters of the
       
   101 `ORM`_ layer. They tell which attributes should be loaded at once on
       
   102 entity object instantiation (by default, only the eid is known, other
       
   103 attributes are loaded on demand), and which attribute is to be used to
       
   104 order the .related() and .unrelated() methods output.
       
   105 
       
   106 Finally, we can observe the big TICKET_DEFAULT_STATE_RESTR is a pure
       
   107 application domain piece of data. There is, of course, no limitation
       
   108 to the amount of class attributes of this kind.
       
   109 
       
   110 Let us now dig into more substantial pieces of code.
       
   111 
       
   112 .. sourcecode:: python
       
   113 
       
   114     def latest_version(self, states=('published',), reverse=None):
       
   115         """returns the latest version(s) for the project in one of the given
       
   116         states.
       
   117 
       
   118         when no states specified, returns the latest published version.
       
   119         """
       
   120         order = 'DESC'
       
   121         if reverse is not None:
       
   122             warn('reverse argument is deprecated',
       
   123                  DeprecationWarning, stacklevel=1)
       
   124             if reverse:
       
   125                 order = 'ASC'
       
   126         rset = self.versions_in_state(states, order, True)
       
   127         if rset:
       
   128             return rset.get_entity(0, 0)
       
   129         return None
       
   130 
       
   131     def versions_in_state(self, states, order='ASC', limit=False):
       
   132         """returns version(s) for the project in one of the given states, sorted
       
   133         by version number.
       
   134 
       
   135         If limit is true, limit result to one version.
       
   136         If reverse, versions are returned from the smallest to the greatest.
       
   137         """
       
   138         if limit:
       
   139             order += ' LIMIT 1'
       
   140         rql = 'Any V,N ORDERBY version_sort_value(N) %s ' \
       
   141               'WHERE V num N, V in_state S, S name IN (%s), ' \
       
   142               'V version_of P, P eid %%(p)s' % (order, ','.join(repr(s) for s in states))
       
   143         return self._cw.execute(rql, {'p': self.eid})
       
   144 
       
   145 .. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker/
       
   146 
       
   147 These few lines exhibit the important properties we want to outline:
       
   148 
       
   149 * entity code is concerned with the application domain
       
   150 
       
   151 * it is NOT concerned with database coherency (this is the realm of
       
   152   Hooks/Operations); in other words, it assumes a coherent world
       
   153 
       
   154 * it is NOT concerned with end-user interfaces
       
   155 
       
   156 * however it can be used in both contexts
       
   157 
       
   158 * it does not create or manipulate the internal object's state
       
   159 
       
   160 * it plays freely with RQL expression as needed
       
   161 
       
   162 * it is not concerned with internationalization
       
   163 
       
   164 * it does not raise exceptions
       
   165 
       
   166