预览ExtJS 4.0的新功能(三):客户端数据层的革新:引入ActiveRecord模式

转载请注明出处Ext中文网http://ajaxjs.com)。

Ext JS 4最强大的机能之一就是将模型的关系映射链接到一起。在 Ext 4 数据模型中,这种链接关系是通过关联操作(associations)来完成的。在应用中定义不同物件的关系是非常自然的。比如说,在一个食谱数据库中,一条食谱可能会有多条评论,多条评论又可能为同一作者所写,而作者又可以创造多条食谱。透过定义这种链接关系可以让你以更直观地更强大的方式去操纵数据。

一、预备知识

首先是 belongTo 管关联。何谓“belongTo”,我们不妨这样说(“属于”这里表示特定的关系):

  • 公司数据库中,账单 accout 属于 公司 company;
  • 论坛程序中,帖子 thread 属于论坛 forum,也属于分类 cateory;
  • 一个画册中,缩略图 thumbnail 属于picture。

如果 bar 属于foo,也就是说它们之间的关系 belongTo,那么一般情况下,关系型数据库物理表中,bar 表会有一称作 foo_id 的字段,作为外键(foreign_key)出现。

相对地,与 belongs_to 关联相对应地就是 hasMany 关联,也就是“多对一” v.s “一对多”之间的区别;

也许我们可以借助“父-子”的概念去理解,例如父母可有多个孩子,孩子只有一对父母。账单相当于公司是“子级别”,而公司相当于账单是“父级别”。

当前 Ext.data 可支持 belongTo(多对一)、hasMany(一对多)、多对多。而 hasOne 关系实际上包含在 belongTo 关系中。

二、Model 实例管理器:ModelMgr

例如,假设一个博客的管理程序,有用户 Users、贴子 Posts 和评论 Comments 三大业务对象。我们可以用以下语法表达它们之间的关系:

Edit 2011-9-22 示例代码可能与最新版本的 Ext Model 有区别,但不影响主干意思——感谢 Thanks to QiuQiu/太阳提醒。

Ext.regModel('Post', {
fields: ['id', 'user_id'],
belongsTo: 'User',
hasMany : {model: 'Comment', name: 'comments'}
});
Ext.regModel('Comment', {
fields: ['id', 'user_id', 'post_id'],
belongsTo: 'Post'
});
Ext.regModel('User', {
fields: ['id'],
hasMany: [
'Post',
{model: 'Comment', name: 'comments'}
]
});

通过定义属性 associations 亦可:

Ext.regModel('User', {
fields: ['id'],
associations: [
{type: 'hasMany', model: 'Post', name: 'posts'},
{type: 'hasMany', model: 'Comment', name: 'comments'}
]
});

在登记模型的过程中,主要执行的程序如下(详细见注释):

/**
* 登记一个模型的定义。所有模型的插件会被立即启动,插件的参数就来自于model的配置项。
*/
registerType: function(name, config) {
…… ……

//if we're extending another model, inject its fields, associations and validations
if (extendName) {
// 现有的业务类,继承之
extendModel = this.types[extendName];
extendModelProto = extendModel.prototype;
extendValidations = extendModelProto.validations;

proxy = extendModel.proxy;
fields = extendModelProto.fields.items.concat(fields);
associations = extendModelProto.associations.items.concat(associations);
config.validations = extendValidations ? extendValidations.concat(config.validations) : config.validations;
} else {
// 从Ext.data.Model继承,诞生全新的业务类。
extendModel = Ext.data.Model;
proxy = config.proxy;
}
// 创建新的业务类/
model = Ext.extend(extendModel, config);
// 初始化插件
for (i = 0, length = modelPlugins.length; i < length; i++) {
plugins.push(PluginMgr.create(modelPlugins[i]));
}
// 保存model到ModelMgr
this.types[name] = model;
// override方法修改prototype
Ext.override(model, {
plugins : plugins,
fields : this.createFields(fields),
associations: this.createAssociations(associations, name)
});

model.modelName = name;
// 注意call()用得巧妙!
Ext.data.Model.setProxy.call(model, proxy || this.defaultProxyType);
model.getProxy = model.prototype.getProxy;
// 静态方法
model.load = function() {
Ext.data.Model.load.apply(this, arguments);
};
// 启动插件
for (i = 0, length = plugins.length; i < length; i++) {
plugins[i].bootstrap(model, config);
}

model.defined = true;
this.onModelDefined(model);

return model;
},

三、Ext.data.BelongsToAssociation

Ext.data.Association 表示一对一的关系模型。主模型(owner model)应该有一个外键(a foreign key)的设置,也就是与之关联模型的主键(the primary key)。

var Category = Ext.regModel('Category', {
fields: [
{name: 'id', type: 'int'},
{name: 'name', type: 'string'}
]
});
var Product = Ext.regModel('Product', {
fields: [
{name: 'id', type: 'int'},
{name: 'category_id', type: 'int'},
{name: 'name', type: 'string'}
],
associations: [
{type: 'belongsTo', model: 'Category'}
]
});

上面例子中我们分别创建了 Products 和 Cattegory 模型,然后将它们关联起来,此过程我们可以说产品 Product 是“属于”种类Category的。默认情况下,Product 有一个 category_id 的字段,通过该字段,每个 Product 实体可以与 Category 关联在一起,并在 Product 模型身上产生新的函数。

获得新函数,其原理是通过反射得出的。第一个加入到主模型的函数是 Getter 函数。

var product = new Product({
id: 100,
category_id: 20,
name: 'Sneakers'
});
product.getCategory(function(category, operation) {
//这里可以根据cateory对象来完成一些任务。do something with the category object
alert(category.get('id')); //alerts 20
}, this);

在定义关联关系的时候,就为 Product 模型创建了 getCategory 函数。另外一种 getCategory 函数的用法是送入一个包含 success、failure 和 callback 的对象,都是函数类型。其中,必然一定会调用 callback,而 success 就是成功加载所关联的模型后,才会调用的 success 的函数;反之没有加载关联模型,就执行 failure 函数。

product.getCategory({
callback: function(category, operation), //一定会调用的函数。a function that will always be called
success : function(category, operation), //成功时调用的函数。a function that will only be called if the load succeeded
failure : function(category, operation), //失败时调用的函数。a function that will only be called if the load did not succeed
scope : this // 作用域对象是一个可选的参数,其决定了回调函数中的作用域。optionally pass in a scope object to execute the callbacks in
});

以上的回调函数执行时带有两个参数:1、所关联的模型之实例;2、负责加载模型实例的Ext.data.Operation对象。当加载实例有问题时,Operation对象就非常有用。

第二个生成的函数设置了关联的模型实例。如果只传入一个参数到setter那么下面的两个调用是一致的:

// this call
product.setCategory(10);
//is equivalent to this call:
product.set('category_id', 10);

如果传入第二个参数,那么模型会自动保存并且将第二个参数传入到主模型的 Ext.data.Model.save 方法:

product.setCategory(10, function(product, operation) {
//商品已经保持了。the product has been saved
alert(product.get('category_id')); //now alerts 10
});
//另外一种语法: alternative syntax:
product.setCategory(10, {
callback: function(product, operation), //一定会调用的函数。a function that will always be called
success : function(product, operation), //成功时调用的函数。a function that will only be called if the load succeeded
failure : function(product, operation), //失败时调用的函数。a function that will only be called if the load did not succeed
scope : this //作用域对象是一个可选的参数,其决定了回调函数中的作用域。optionally pass in a scope object to execute the callbacks in
})

Model 可以让我们自定义字段参数。若不设置,关联模型的时候会自动根据 primaryKey 和 foreignKey 属性设置。这里我们替换掉了默认的主键(默认为'id')和外键(默认为'category_id')。一般情况却是不需要的。

var Product = Ext.regModel('Product', {
fields: [...],
associations: [
{type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id'}
]
});

四、Ext.data.HasManyAssociation

HasManyAssociation 表示一对多的关系模型。如下例:

Ext.regModel('Product', {
fields: [
{name: 'id', type: 'int'},
{name: 'user_id', type: 'int'},
{name: 'name', type: 'string'}
]
});
Ext.regModel('User', {
fields: [
{name: 'id', type: 'int'},
{name: 'name', type: 'string'}
],
associations: [
{type: 'hasMany', model: 'Product', name: 'products'}
]
});

 

以上我们创建了 Products 和model 模型,我们可以说用户有许多商品。每一个 User 实例都有一个新的函数,此时此刻具体这个函数就是“product”,这正是我们在 name 配置项中所指定的名字。新的函数返回一个特殊的 Ext.data.Store,自动根据模型实例建立产品。

 

// 首先,为user创建一笔新的纪录1。
var user = Ext.ModelMgr.create({id: 1, name: 'Ed'}, 'User');
// 根据既定的关系,创建user.products方法,该方法返回Store对象。
// 创建的Store的作用域自动定义为User的id等于1的产品。
var products = user.products();
// products是一个普通的Store,可以加入轻松地通过add()纪录
products.add({
name: 'Another Product'
});
// 执行Store的保存命令。保存之前都自动哦你将产品的user_id为1。
products.sync();

所述的 Store 只在头一次执行 product() 时实例化,持久化在内存中不会反复创建。

由于 Store 的 API 中自带 filter 过滤器的功能,所以默认下过滤器告诉 Store 只返回关联模型其外键所匹配主模型其主键。例如,用户 User 是 ID=100 拥有的产品 Products,那么过滤器只会返回那些符合 user_id=100 的产品。

但是有些时间必须指定任意字段来过滤,例如 Twitter 搜索的应用程序,我们就需要 Search 和 Tweet 模型:

var Search = Ext.regModel('Search', {
fields: [
'id', 'query'
],
hasMany: {
model: 'Tweet',
name : 'tweets',
filterProperty: 'query'
}
});
Ext.regModel('Tweet', {
fields: [
'id', 'text', 'from_user'
]
});
// 返回filterProperty指定的过程字段。returns a Store filtered by the filterProperty
var store = new Search({query: 'Sencha Touch'}).tweets();

例子中的 tweets 关系约等价于下面代码所示,也就是通过 Ext.data.HasManyAssociation.filterProperty 定义过滤器。

var store = new Ext.data.Store({
model: 'Tweet',
filters: [
{
property: 'query',
value : 'Sencha Touch'
}
]
});

数据可能来源于各个地方,但对于我们来说常见的途径是关系型数据库。本节大部分的内容都是基于关系型数据库进行展开的。

五、Ext.data.PolymorphicAssociation

多对多关系(鉴于文档信息不足……略……)

六、小结

相比于服务端的 ActiveRecord 方案,Ext 只是继承,自然没有太多高级的特性。也许客户端的话,仅此而已便足够……但是有没有人想把 Ext.data放到后台跑呢?天啊~难得不是为了……

从技术评估上看,动态语言比较适合实现所谓的 ActiveRecord,4.0 采用 ActiveRecord 的概念也是处于客户端当中的,生成的不是 SQL,而是通过 AJAX 请求之类的请求,可见这一思路丰富了既 ActiveRecord 的内涵,也从一侧面提升了动态语言的价值。

ORM 几乎是所有企业开发项目的标配,但可实现 ORM 的方案和思路却多种多样。虽不能说百花齐放,但也可以说繁荣到可以说争奇斗艳。

(……ORM 讨论若干字……略……见下面补充的链接)

既然选择了 ActiveRecord 去实现,想必也有一定的理由。JS 是动态语言,动态语言的确很容易做出 ActiveRecrod,这是无疑的,起码比静态语言好做。然而是否一定只选择AcitveRecord 呢?也不见得,例如微软的 JSLinq 也是一种思路,我见过有 JavaScript 方案的(虽然都是对 JSON 查询的,却缺少 JS2SQL 的),说明动态语言的优势还是很明显的,语言包袱没那么重。呵呵,不啰嗦,否则又容易扯起“语言之争”。

实际上模型 ActiveRecord 早已久负盛名,可能这就是 ExtJS 开发团队考量的因素之一。Ruby on Rails、Gails 上的 ActiveRecord 已经热闹非凡,JS 或 Ext 至今才实现的话算晚的了……原来我也写过 ActiveRecord 的 JS,当然差得远了,不过这一切都是没有实际项目的“纸上谈兵”有关概念、理论说明等的内容……还须见企业级开发导师马大叔的为准:http://www.martinfowler.com/eaaCatalog/activeRecord.html……上面权且为草草笔记。

时间: 2024-07-29 09:09:33

预览ExtJS 4.0的新功能(三):客户端数据层的革新:引入ActiveRecord模式的相关文章

预览ExtJS 4.0的新功能(六):读写器/Opeartion

 转载请注明出处Ext中文网 (http://www.ajaxjs.com ).   预览ExtJS 4.0的新功能(六):读写器/Opeartion 许久未更新Blog了,跟大家说声不好意思,要不是工作特忙,以及游戏特吸引人,不然早就写完了,呵呵.当然还离不开夜兄的好言鼓励,一路有大伙总比一个人不知所然强多了. 下面内容大多源自翻译-- Ext.data.Reader 相信我们并不陌生Reader.虽然Ext 4.0的reader依然发挥着它的解析原始数据的职能,主要是转换特定的数据结构,不过

预览ExtJS 4.0的新功能(二):客户端的对象关系映射(ORM)

4.0之前,Ext 的功能已经十分丰富,数据层却难以令人满意.作为 UI 框架,数据层可能不是重点,但明显攸关到业务顺利的展开与否.然而,3.0 之后,虽然过增加了 Ext.data.Api/REST/Direct 等的新协议,但无论 3.0 还是 2.0,Ext.data.* 底层仍维持 1.0 的对象模型.越来越多的新协议和新机制加入到 Ext.data.*后,反而造成 API 逾加凌乱.这样就有了改造 data 的必要了.可喜的是,4.0 中已经重构 Ext.data.* 部分,重点解决旧

预览ExtJS 4.0的新功能(五):焕然一新的Store/Proxy

接上文 Proxy Ext.data.Proxy 是一个十分关键的数据通讯类.Proxy 直译"代理",可数据怎么能够代理?有点不通!Proxy 到底指的是什么意思呢? 依小弟浅见,定义 Proxy 就是定义数据源,至少 Ext 中 AjaxProxy/ScriptProxy/LocalProxy/SessionProxy 表现如此.当 Store 要为Ext.data.Model4 加载或者保存数据的时候,必须通过这个 Proxy 类.具体说,Proxy的操作就是将所有最终的数据操作

预览ExtJS 4.0的新功能(四):焕然一新的Store/Proxy

焕然一新的Store/Proxy 转载请注明出处Ext中文网 (http://www.ajaxjs.com) . 上期讲到,Ext JS4 可以允许我们定义业务模型之间的关系(Association),在浏览器客户端就可以描述我们的业务模型如何,甚至灵活调用域对象(Domain Object)也不是一件难事.可是如果没用实体数据,业务模型也只是"空壳",缺了内容也不存在什么"UI 数据绑定"一说.当然,Ext 中,我们可以为 Model 调入与之 Schmea 匹配

预览ExtJS 4.0的新功能/新特性(一):渲染组件的方式

转载请注明出处Ext中文网(http://www.ajaxjs.com). ExtJS 3.3的下一个版本就是4.0.--什么!?您不知道?那就让我们为你展开新一段的 Ext 之旅吧! 一.渲染组件的方式 话说 ExtJS Roadmap(新版本线路图)其中重要的一项就是"Rearchitected component rendering system - smaller, faster and simpler than ever before",重新编制组件的渲染架构,目标是比以前更

ASP.NET 2.0的新功能

ASP.NET 2.0的新功能 ASP.NET 2.0提高ASP.NET通过增加一些新的特点. -------------------------------------------------- ------------------------------ 改进ASP.NET 2.0中 ASP.NET 2.0的目的是使Web开发更容易和更快. 设计目标为ASP.NET 2.0 : 提高生产率,消除70 %的代码 使用相同的控制,所有类型的设备 提供一个更快更好的Web服务器平台 简化汇编和安装

《圣殿祭司的ASP.NET4.0专家技术手册》----1-2 .NET 4.0平台新功能概览

1-2 .NET 4.0平台新功能概览 圣殿祭司的ASP.NET4.0专家技术手册 .NET平台版本从1.0.1.1.2.0.3.0.3.5不断演进.最新4.0版的重要改进如下: NET Framework 4.0 Visual C# 2010 Visual Basic 2010 Visual F# 2010(或F# 2.0) 新的CLR 4.0运行时 DLR动态语言运行时(Dynamic Language Runtime) ASP.NET 4.0 ASP.NET MVC 2.0(最新版为4.0

《圣殿祭司的ASP.NET4.0专家技术手册》----2-1 C# 4.0语言新功能

2-1 C# 4.0语言新功能 圣殿祭司的ASP.NET4.0专家技术手册 C# 4.0新增的语言功能,除了命名实参和可选实参(VB早已支持)较为常用,其他功能几乎是从动态基础衍生出来的便利性或灵活性,新功能如下: 命名实参和可选实参(Named and Optional Arguments): Dynamic动态类型(Dynamic Type): 共变量与反变量(Covariance and Contravariance): Office可程序性支持(Office Programmabilit

Win8.1预览版升级指南及新特性体验

  Windows 8.1预览版升级前的准备 Windows 8.1预览版激动人心地在今天提供了下载,用户想知道传统开始菜单是否恢复?操作系统界面及功能又有何新鲜改进?答案等新系统安装后一定揭晓!小编这就为大家带来Windows 8.1预览版的安装升级指南. 微软官方提供升级和全新安装两种方案 方案一:通过Windows8商店升级 在原Windows8系统的基础上进行升级:通过下载官方更新补丁程序,再连接Windows8应用商店升级系统. 方案二:通过ISO文件全新安装 无法直接升级系统的用户,