[doc/book] a new chapter on how to use the ORM stable
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Fri, 02 Apr 2010 17:27:53 +0200
branchstable
changeset 5144 5a09bea07302
parent 5143 43afbdd5c8b4
child 5145 bfa4d775219f
[doc/book] a new chapter on how to use the ORM
doc/book/en/development/entityclasses/application-logic.rst
doc/book/en/development/entityclasses/data-as-objects.rst
doc/book/en/development/entityclasses/index.rst
doc/book/en/development/entityclasses/interfaces.rst
doc/book/en/development/entityclasses/more.rst
doc/book/en/intro/concepts/index.rst
doc/book/en/intro/history.rst
entity.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/entityclasses/application-logic.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -0,0 +1,158 @@
+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
+
+
--- a/doc/book/en/development/entityclasses/data-as-objects.rst	Fri Apr 02 16:10:35 2010 +0200
+++ b/doc/book/en/development/entityclasses/data-as-objects.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -17,6 +17,7 @@
 :Formatting and output generation:
 
   * `view(vid, **kwargs)`, applies the given view to the entity
+    (and returns an unicode string)
 
   * `absolute_url(**kwargs)`, returns an absolute URL to access the primary view
     of an entity
@@ -32,22 +33,30 @@
   * `as_rset()`, converts the entity into an equivalent result set simulating the
      request `Any X WHERE X eid _eid_`
 
-  * `complete(skip_bytes=True)`, executes a request that recovers all at once
-    all the missing attributes of an entity
+  * `complete(skip_bytes=True)`, executes a request that recovers at
+    once all the missing attributes of an entity
 
   * `get_value(name)`, returns the value associated to the attribute name given
     in parameter
 
-  * `related(rtype, x='subject', limit=None, entities=False)`, returns a list
-    of entities related to the current entity by the relation given in parameter
+  * `related(rtype, role='subject', limit=None, entities=False)`,
+    returns a list of entities related to the current entity by the
+    relation given in parameter
 
-  * `unrelated(rtype, targettype, x='subject', limit=None)`, returns a result set
-    corresponding to the entities not related to the current entity by the
-    relation given in parameter and satisfying its constraints
+  * `unrelated(rtype, targettype, role='subject', limit=None)`,
+    returns a result set corresponding to the entities not (yet)
+    related to the current entity by the relation given in parameter
+    and satisfying its constraints
 
   * `set_attributes(**kwargs)`, updates the attributes list with the corresponding
     values given named parameters
 
+  * `set_relations(**kwargs)`, add relations to the given object. To
+     set a relation where this entity is the object of the relation,
+     use 'reverse_'<relation> as argument name.  Values may be an
+     entity, a list of entities, or None (meaning that all relations of
+     the given type from or to this object should be deleted).
+
   * `copy_relations(ceid)`, copies the relations of the entities having the eid
     given in the parameters on the current entity
 
@@ -66,8 +75,10 @@
 and helps specializing (by further subclassing) the handling of a
 given entity type.
 
-The methods defined for `AnyEntity`, in addition to `Entity`, are the
-following ones:
+Most methods defined for `AnyEntity`, in addition to `Entity`, add
+support for the `Dublin Core`_ metadata.
+
+.. _`Dublin Core`: http://dublincore.org/
 
 :Standard meta-data (Dublin Core):
 
@@ -85,12 +96,26 @@
   * `dc_authors()`, returns a unicode string corresponding to the meta-data
     `Authors` (owners by default)
 
+  * `dc_creator()`, returns a unicode string corresponding to the
+    creator of the entity
+
   * `dc_date(date_format=None)`, returns a unicode string corresponding to
     the meta-data `Date` (update date by default)
 
   * `dc_type(form='')`, returns a string to display the entity type by
     specifying the preferred form (`plural` for a plural form)
 
+  * `dc_language()`, returns the language used by the entity
+
+
+:Misc methods:
+
+  * `after_deletion_path`, return (path, parameters) which should be
+     used as redirect information when this entity is being deleted
+
+  * `pre_web_edit`, callback called by the web editcontroller when an
+    entity will be created/modified, to let a chance to do some entity
+    specific stuff (does nothing by default)
 
 Inheritance
 -----------
--- a/doc/book/en/development/entityclasses/index.rst	Fri Apr 02 16:10:35 2010 +0200
+++ b/doc/book/en/development/entityclasses/index.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -10,4 +10,4 @@
    data-as-objects
    load-sort
    interfaces
-   more
+   application-logic
--- a/doc/book/en/development/entityclasses/interfaces.rst	Fri Apr 02 16:10:35 2010 +0200
+++ b/doc/book/en/development/entityclasses/interfaces.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -1,7 +1,9 @@
 Interfaces
 ----------
 
-Same thing as object-oriented programming 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):
@@ -17,7 +19,7 @@
             """returns the item's children"""
 
         def children_rql(self):
-            """XXX returns RQL to get children"""
+            """returns RQL to get children"""
 
         def iterchildren(self):
             """iterates over the item's children"""
--- a/doc/book/en/development/entityclasses/more.rst	Fri Apr 02 16:10:35 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Navigation on deletion
-----------------------
-
-XXX after_deletion_path, pre_web_edit
-
-Controlling output url
------------------------
-
-XXX write me
-
-Controling notification references
-----------------------------------
-
-XXX write me
--- a/doc/book/en/intro/concepts/index.rst	Fri Apr 02 16:10:35 2010 +0200
+++ b/doc/book/en/intro/concepts/index.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -113,11 +113,12 @@
 The web engine replies to http requests and runs the user interface
 and most of the application logic.
 
-By default the web engine provides a default user interface based on
+By default the web engine provides a `CRUD`_ user interface based on
 the data model of the instance. Entities can be created, displayed,
 updated and deleted. As the default user interface is not very fancy,
 it is usually necessary to develop your own.
 
+.. _`CRUD`: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
 
 .. _SchemaIntro:
 
@@ -331,4 +332,4 @@
 cubicweb application.
 
 
-.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
+.. |cubicweb| replace:: *CubicWeb*
--- a/doc/book/en/intro/history.rst	Fri Apr 02 16:10:35 2010 +0200
+++ b/doc/book/en/intro/history.rst	Fri Apr 02 17:27:53 2010 +0200
@@ -19,10 +19,10 @@
 *CubicWeb* has been developed by Logilab_ and used in-house for many years
 before it was first installed for its clients in 2006 as version 2.
 
-In 2008, *CubicWeb* version 3 became downloadable for free under the terms of
-the LGPL license. Its community is now steadily growing as changes can occur
-rapidly thanks to the time and energy originally put in the design of the
-framework.
+In 2008, *CubicWeb* version 3 became downloadable for free under the
+terms of the LGPL license. Its community is now steadily growing
+without hampering the fast-paced stream of changes thanks to the time
+and energy originally put in the design of the framework.
 
 
 .. _Narval: http://www.logilab.org/project/narval
--- a/entity.py	Fri Apr 02 16:10:35 2010 +0200
+++ b/entity.py	Fri Apr 02 17:27:53 2010 +0200
@@ -901,7 +901,7 @@
         """add relations to the given object. To set a relation where this entity
         is the object of the relation, use 'reverse_'<relation> as argument name.
 
-        Values may be an entity, a list of entity, or None (meaning that all
+        Values may be an entity, a list of entities, or None (meaning that all
         relations of the given type from or to this object should be deleted).
         """
         # XXX update cache