|
1 How to use entities objects and adapters |
|
2 ---------------------------------------- |
|
3 |
|
4 The previous chapters detailed the classes and methods available to |
|
5 the developer 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 (and their adapters) are used in the repository and |
|
11 web sides of CubicWeb. On the repository side of things, one should |
|
12 manipulate them 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 independent 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 Core |
|
24 methods like ``dc_title``. 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 front-end 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.cw_set(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 an adapter for the concerned entity class(es). But of |
|
46 course, this advice is also reasonable for Hooks/Operations, though |
|
47 the separation of concerns here is less stringent than in the case of |
|
48 views. |
|
49 |
|
50 This leads to the practical role of objects adapters: it's where an |
|
51 important part of the application logic lies (the other part being |
|
52 located in the Hook/Operations). |
|
53 |
|
54 Anatomy of an entity class |
|
55 -------------------------- |
|
56 |
|
57 We can look now at a real life example coming from the `tracker`_ |
|
58 cube. Let us begin to study the ``entities/project.py`` content. |
|
59 |
|
60 .. sourcecode:: python |
|
61 |
|
62 from cubicweb.entities.adapters import ITreeAdapter |
|
63 |
|
64 class ProjectAdapter(ITreeAdapter): |
|
65 __select__ = is_instance('Project') |
|
66 tree_relation = 'subproject_of' |
|
67 |
|
68 class Project(AnyEntity): |
|
69 __regid__ = 'Project' |
|
70 fetch_attrs, cw_fetch_order = fetch_config(('name', 'description', |
|
71 'description_format', 'summary')) |
|
72 |
|
73 TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")' |
|
74 |
|
75 def dc_title(self): |
|
76 return self.name |
|
77 |
|
78 The fact that the `Project` entity type implements an ``ITree`` |
|
79 interface is materialized by the ``ProjectAdapter`` class (inheriting |
|
80 the pre-defined ``ITreeAdapter`` whose ``__regid__`` is of course |
|
81 ``ITree``), which will be selected on `Project` entity types because |
|
82 of its selector. On this adapter, we redefine the ``tree_relation`` |
|
83 attribute of the ``ITreeAdapter`` class. |
|
84 |
|
85 This is typically used in views concerned with the representation of |
|
86 tree-like structures (CubicWeb provides several such views). |
|
87 |
|
88 It is important that the views themselves try not to implement this |
|
89 logic, not only because such views would be hardly applyable to other |
|
90 tree-like relations, but also because it is perfectly fine and useful |
|
91 to use such an interface in Hooks. |
|
92 |
|
93 In fact, Tree nature is a property of the data model that cannot be |
|
94 fully and portably expressed at the level of database entities (think |
|
95 about the transitive closure of the child relation). This is a further |
|
96 argument to implement it at entity class level. |
|
97 |
|
98 ``fetch_attrs`` configures which attributes should be pre-fetched when using ORM |
|
99 methods retrieving entity of this type. In a same manner, the ``cw_fetch_order`` is |
|
100 a class method allowing to control sort order. More on this in :ref:`FetchAttrs`. |
|
101 |
|
102 We can observe the big ``TICKET_DEFAULT_STATE_RESTR`` is a pure |
|
103 application domain piece of data. There is, of course, no limitation |
|
104 to the amount of class attributes of this kind. |
|
105 |
|
106 The ``dc_title`` method provides a (unicode string) value likely to be |
|
107 consumed by views, but note that here we do not care about output |
|
108 encodings. We care about providing data in the most universal format |
|
109 possible, because the data could be used by a web view (which would be |
|
110 responsible of ensuring XHTML compliance), or a console or file |
|
111 oriented output (which would have the necessary context about the |
|
112 needed byte stream encoding). |
|
113 |
|
114 .. note:: |
|
115 |
|
116 The Dublin Core `dc_xxx` methods are not moved to an adapter as they |
|
117 are extremely prevalent in CubicWeb and assorted cubes and should be |
|
118 available for all entity types. |
|
119 |
|
120 Let us now dig into more substantial pieces of code, continuing the |
|
121 Project class. |
|
122 |
|
123 .. sourcecode:: python |
|
124 |
|
125 def latest_version(self, states=('published',), reverse=None): |
|
126 """returns the latest version(s) for the project in one of the given |
|
127 states. |
|
128 |
|
129 when no states specified, returns the latest published version. |
|
130 """ |
|
131 order = 'DESC' |
|
132 if reverse is not None: |
|
133 warn('reverse argument is deprecated', |
|
134 DeprecationWarning, stacklevel=1) |
|
135 if reverse: |
|
136 order = 'ASC' |
|
137 rset = self.versions_in_state(states, order, True) |
|
138 if rset: |
|
139 return rset.get_entity(0, 0) |
|
140 return None |
|
141 |
|
142 def versions_in_state(self, states, order='ASC', limit=False): |
|
143 """returns version(s) for the project in one of the given states, sorted |
|
144 by version number. |
|
145 |
|
146 If limit is true, limit result to one version. |
|
147 If reverse, versions are returned from the smallest to the greatest. |
|
148 """ |
|
149 if limit: |
|
150 order += ' LIMIT 1' |
|
151 rql = 'Any V,N ORDERBY version_sort_value(N) %s ' \ |
|
152 'WHERE V num N, V in_state S, S name IN (%s), ' \ |
|
153 'V version_of P, P eid %%(p)s' % (order, ','.join(repr(s) for s in states)) |
|
154 return self._cw.execute(rql, {'p': self.eid}) |
|
155 |
|
156 .. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker/ |
|
157 |
|
158 These few lines exhibit the important properties we want to outline: |
|
159 |
|
160 * entity code is concerned with the application domain |
|
161 |
|
162 * it is NOT concerned with database consistency (this is the realm of |
|
163 Hooks/Operations); in other words, it assumes a consistent world |
|
164 |
|
165 * it is NOT (directly) concerned with end-user interfaces |
|
166 |
|
167 * however it can be used in both contexts |
|
168 |
|
169 * it does not create or manipulate the internal object's state |
|
170 |
|
171 * it plays freely with RQL expression as needed |
|
172 |
|
173 * it is not concerned with internationalization |
|
174 |
|
175 * it does not raise exceptions |
|
176 |
|
177 |