4.7. 如何使用实体对象和适配器¶
前几章详细介绍了在所谓的 ORM 水平。然而,他们很少提及这些对象的常见使用模式。
实体对象(及其适配器)用于CubicWeb的存储库和Web端。在事物的存储库方面,应该在钩子和操作中操作它们。
钩子和操作为规则的实现提供支持,例如计算属性、一致性不变量等(它们与数据库触发器具有相同的作用,但以独立于实际数据源的方式)。
因此,应用程序的许多业务规则将用钩子(或操作)编写。
在Web端,视图通常也使用实体对象进行操作。在视图中使用的明显实体方法是都柏林的核心方法,例如 dc_title
.出于关注点分离的原因,应该确保没有任何UI逻辑渗透到实体级别,也不应该有任何业务逻辑渗透到视图中。
在事务的持续时间内,实体对象可以在视图和挂钩中被多次实例化,甚至对于同一个数据库实体也是如此。例如,在经典的CubicWeb部署设置中,存储库和Web前端是通过线进行通信的独立进程。这些进程之间无法共享状态(有特定的API)。因此,不可能在应用程序的这些组件之间使用实体对象作为信使。这意味着属性集 obj.x = 42
,无论x是否实际是实体模式属性,其寿命都很短,仅限于在其中构建对象的挂钩、操作或视图。
可以在挂钩/操作的上下文中使用 obj.cw_set(x=42)
符号或普通RQL SET
表达式。
在视图中,最好将必要的逻辑封装在相关实体类的适配器方法中。但是,当然,这个建议对于钩子/操作也是合理的,尽管这里的关注点分离不如视图分离那么严格。
这导致了对象适配器的实际作用:它是应用程序逻辑的一个重要部分(另一部分位于hook/操作中)。
4.8. 实体类的解剖¶
我们现在可以看到来自 tracker 立方体。让我们开始学习 entities/project.py
内容。
from cubicweb.entities.adapters import ITreeAdapter
class ProjectAdapter(ITreeAdapter):
__select__ = is_instance('Project')
tree_relation = 'subproject_of'
class Project(AnyEntity):
__regid__ = 'Project'
fetch_attrs, cw_fetch_order = fetch_config(('name', 'description',
'description_format', 'summary'))
TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")'
def dc_title(self):
return self.name
事实上, Project entity type implements an ITree
interface is materialized by the ProjectAdapter
class (inheriting the pre-defined ITreeAdapter
whose _ _当然了。 ``ITree
,将在上选择 Project 实体类型,因为它的选择器。在这个适配器上,我们重新定义 tree_relation
的属性 ITreeAdapter
班级。
这通常用于与类树结构的表示有关的视图(CubicWeb提供了几个这样的视图)。
重要的是视图本身不尝试实现这个逻辑,不仅因为这样的视图很难应用于其他类树关系,而且因为在钩子中使用这样的接口是非常好和有用的。
事实上,树的性质是数据模型的一个属性,不能在数据库实体的级别上完全和可移植地表示(考虑子关系的可传递闭包)。这是在实体类级别实现它的另一个参数。
fetch_attrs
配置在使用ORM方法检索此类型的实体时应预取哪些属性。以同样的方式, cw_fetch_order
是允许控制排序顺序的类方法。关于这个的更多信息 加载的属性和默认排序管理 .
我们可以观察到 TICKET_DEFAULT_STATE_RESTR
是纯应用程序域数据块。当然,这种类属性的数量没有限制。
这个 dc_title
方法提供了一个可能被视图使用的(unicode字符串)值,但请注意,这里我们不关心输出编码。我们关心以尽可能通用的格式提供数据,因为这些数据可以由Web视图(负责确保XHTML的遵从性)或控制台或面向文件的输出(具有所需字节流编码的必要上下文)使用。
注解
都柏林核心区 dc_xxx 方法不会移动到适配器中,因为它们在CubicWeb和各种多维数据集中非常普遍,应该可以用于所有实体类型。
现在让我们深入研究更多实质性的代码片段,继续这个项目类。
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})
这几行显示了我们想要概述的重要特性:
实体代码与应用程序域有关
它与数据库一致性无关(这是钩子/操作的领域);换句话说,它假定一个一致的世界
它(直接)与最终用户界面无关。
但是它可以在两种情况下使用
它不会创建或操作内部对象的状态
它可以根据需要自由地使用rql表达式。
它与国际化无关
它不会引发异常