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

简介: 转载请注明出处Ext中文网 (http://ajaxjs.com)。 Ext JS 4最强大的机能之一就是将模型的关系映射链接到一起。在 Ext 4 数据模型中,这种链接关系是通过关联操作(associations)来完成的。

转载请注明出处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……上面权且为草草笔记。

目录
相关文章
|
12月前
|
移动开发 JavaScript 小程序
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(一)(下)
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(一)
|
6天前
鸿蒙使用 @Builder扩展出来的布局数据更新没法更新UI
鸿蒙使用 @Builder扩展出来的布局数据更新没法更新UI
20 1
|
12月前
|
开发框架 JavaScript 小程序
扩展应用功能的无限可能——UniApp生态系统中的组件库与插件探索
扩展应用功能的无限可能——UniApp生态系统中的组件库与插件探索
|
12月前
|
开发框架 JavaScript API
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(二)(下)
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(二)
|
12月前
|
移动开发 JavaScript 小程序
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(一)(上)
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(一)
|
12月前
|
开发框架 前端开发 JavaScript
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(二)(上)
扩展应用功能的无限可能——UniApp生态系统中的组件库探索(二)
|
监控 测试技术 API
【更新】Eolink Apikit 10.9.0 版本:接口测试支持通过 URL 请求大型文件,支持左右视图和全屏视图
本次更新后,会把 API 管理、自动化测试、API 监控中的环境和自定义函数数据进行合并统一管理。 1) 环境合并:各应用级环境合并成空间级环境后,直接罗列在空间级环境列表中,不进行去重,故可能会有重名环境需要大家按需处理。 2) 自定义函数合并:各应用自定义函数合并成空间级自定义函数后,在空间级自定义函数分组中会增加三个一级分组“API 管理函数”、“自动化测试函数”、“API 监控函数”,各应用自定义函数会置于对应的应用分组下,并且进行同名去重,保留最新编辑过的自定义函数。
92 0
【更新】Eolink Apikit 10.9.0 版本:接口测试支持通过 URL 请求大型文件,支持左右视图和全屏视图
|
前端开发
封装库/工具库中重要概念之组件库
前端开发中,封装库和工具库是非常重要的组成部分。它们可以帮助我们提高代码复用性和可维护性,从而缩短开发周期和降低维护成本。在封装库和工具库中,组件库是其中最为重要和常用的一种,因为它们可以帮助我们快速构建复杂的 UI 界面。
229 0
如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理
I was asked by one partner that it is expected to adapt the label of “New” button into “Add”, and change the text of first menu item from “Add” to “From Contact”.
如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理