|
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 |