写在前面
最近比较忙,换了新工作还要学习很多全新的技术栈,并给自己找了很多借口来不去坚持写博客。常常具有讽刺意味的是,更多剩下的时间并没有利用而更多的是白白浪费,也许这就是青春吧,挥霍吧,这不是我想要的,既然这样,我还要继续写下去,坚持把博客做好,争取进前100博客,在此谨记。
2015年5月7日深夜,于电脑旁。
文章索引
定义模型
Ember-data中所有的模型都继承自DS.Model,模型内部的属性是通过DS.attr来声明的。
var attr = DS.attr; App.Person = DS.Model.extend({ firstName: attr(), lastName: attr(), birthday: attr() });
此外,DS.Model中还可以添加“计算属性”:
App.Person = Ember.Object.extend({ // these will be supplied by `create` firstName: null, lastName: null, fullName: function() { return this.get(‘firstName‘) + ‘ ‘ + this.get(‘lastName‘); }.property(‘firstName‘, ‘lastName‘) }); var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark" }); ironMan.get(‘fullName‘) // "Tony Stark"
特别的,还可以显式指定属性返回值类型:
App.Person = DS.Model.extend({ birthday: DS.attr(‘date‘) });
默认情况下,REST 适配器支持的属性类型有string
, number
, boolean
和date
。 传统的适配器会提供额外的属性类型,并支持你注册自定义的属性类型。 详情请查看documentation section on the REST Adapter。
请注意:date类型参考ISO8601,例如:2014-05-27T12:54:01
选项
目前,Ember-data只支持一种默认的选项:
export default DS.Model.extend({ username: DS.attr(‘string‘), email: DS.attr(‘string‘), verified: DS.attr(‘boolean‘, {defaultValue: false}), createdAt: DS.attr(‘string‘, { defaultValue: function() { return new Date(); } }) });
定义关联模型
Ember Data 包括了几个内置的关联类型,以帮助你确定你的模型如何相互关联的。
一对一
使用DS.belongsTo
在两个模型间声明一对一的关系。
App.User = DS.Model.extend({ profile: DS.belongsTo(‘profile‘) }); App.Profile = DS.Model.extend({ user: DS.belongsTo(‘user‘) });
一对多
使用DS.belongsTo
结合DS.hasMany
来声明两个模型间的一对多关系,示例如下:
App.Post = DS.Model.extend({ comments: DS.hasMany(‘comment‘) }); App.Comment = DS.Model.extend({ post: DS.belongsTo(‘post‘) });
多对多
使用DS.hasMany
来声明两个模型间的多对多关系。
App.Post = DS.Model.extend({ tags: DS.hasMany(‘tag‘) }); App.Tag = DS.Model.extend({ posts: DS.hasMany(‘post‘) });
显式反转
Ember Data会尽最大努力去自动发现关联关系的映射关系。在上例的一对多的情况下,修改了comments
会自动更新post
,应为这是唯一的一个关联模型。
但是,有时候对同一个类型有多个belongsTo
/hasMany
关联关系。这时可以通过指定在反向端使用DS.hasMany
的inverse
选项来指定其关联的模型:
var belongsTo = DS.belongsTo, hasMany = DS.hasMany; App.Comment = DS.Model.extend({ onePost: belongsTo("post"), twoPost: belongsTo("post"), redPost: belongsTo("post"), bluePost: belongsTo("post") }); App.Post = DS.Model.extend({ comments: hasMany(‘comment‘, { inverse: ‘redPost‘ }) });
当然也可以在belongsTo
一侧指定,它将按照预期那样工作。
创建和删除记录
创建
通过调用仓库的createRecord
方法,可以创建记录:
store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘ });
尽管createRecord
的使用已经非常直接,但是还需要注意一点,就是目前还不支持将一个承诺赋值给一个关联。
例如,如果希望给文章设置author
属性,如果指定ID的user
并没有加载到仓库中的话,下面的代码将不会正常工作。
var store = this.store; store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘, author: store.find(‘user‘, 1) });
不过在承诺履行时可以非常方便的进行关联关系的设置:
var store = this.store; var post = store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘ }); store.find(‘user‘, 1).then(function(user) { post.set(‘author‘, user); });
删除
删除记录与创建记录一样简单。只需要调用DS.Model
实例的deleteRecord()
方法即可。这将会吧记录标记为isDeleted
,并且不在store
的all()
查询中返回。删除操作之后会通过使用save()
来进行持久化。此外,也可以使用destroyRecord
来将删除和持久化一次完成。
var post = store.find(‘post‘, 1); post.deleteRecord(); post.get(‘isDeleted‘); // => true post.save(); // => DELETE to /posts/1 // OR var post = store.find(‘post‘, 2); post.destroyRecord(); // => DELETE to /posts/2
将记录推送进仓库
上节讲到仓库的概念,仓库类似于数据的缓冲池,当应用程序向仓库中查找不存在数据时,仓库则向数据源发起数据请求,如果存在的话,直接返回仓库中的数据。
推送记录
为了将记录推入仓库,需要调用仓库的push()
方法。
var attr = DS.attr; App.Album = DS.Model.extend({ title: attr(), artist: attr(), songCount: attr() }); App.ApplicationRoute = Ember.Route.extend({ model: function() { this.store.push(‘album‘, { id: 1, title: "Fewer Moving Parts", artist: "David Bazan", songCount: 10 }); this.store.push(‘album‘, { id: 2, title: "Calgary b/w I Can‘t Make You Love Me/Nick Of Time", artist: "Bon Iver", songCount: 2 }); } });
持久化记录
Ember Data中的记录都基于实例来进行持久化。调用DS.Model
实例的save()
会触发一个网络请求,来进行记录的持久化。
var post = store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘ }); post.save(); // => POST to ‘/posts‘
承诺
save()
会返回一个承诺,这使得可以非常容易的来处理保存成功和失败的场景。下面是一个通用的模式:
var post = store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘ }); var self = this; function transitionToPost(post) { self.transitionToRoute(‘posts.show‘, post); } function failure(reason) { // handle the error } post.save().then(transitionToPost).catch(failure); // => POST to ‘/posts‘ // => transitioning to posts.show route
对于失败的网络请求,承诺也可以方便的来处理:
var post = store.createRecord(‘post‘, { title: ‘Rails is Omakase‘, body: ‘Lorem ipsum‘ }); var onSuccess = function(post) { this.transitionToRoute(‘posts.show‘, post); }; var onFail = function(post) { // deal with the failure here }; post.save().then(onSuccess, onFail); // => POST to ‘/posts‘ // => transitioning to posts.show route
更多关于承诺的内容请参看这里,下面是一个示例展示了如何在重试失败的持久化操作:
function retry(callback, nTimes) { // if the promise fails return callback().fail(function(reason) { // if we haven‘t hit the retry limit if (nTimes-- > 0) { // retry again with the result of calling the retry callback // and the new retry limit return retry(callback, nTimes); } // otherwise, if we hit the retry limit, rethrow the error throw reason; }); } // try to save the post up to 5 times retry(function() { return post.save(); }, 5);
查询记录
Ember Data仓库提供了一个非常简单的查询一类记录的接口,该接口就是store
对象的find
方法。在内部,store
根据传入的参数使用find
、findAll
和findQuery
完成查询。store.find()
的第一个参数是记录的类型,第二个可选参数确定查询是获取所有记录,还是一条记录,还是特定的记录。
查询一个类型的所有记录
var posts = this.store.find(‘post‘);
如果希望获取已经加载到仓库中的记录的列表,而不希望通过一个网络请求去获取,可以使用all
方法:
var posts = this.store.all(‘post‘); // => no network request
find
会返回一个将使用DS.RecordArray
来履行的DS.PromiseArray
,而all
直接返回DS.RecordArray
。
需要重点注意的一点是DS.RecordArray
不是一个Javascript数组。它是一个实现了Ember.Enumerable
的对象。这一点非常重要,因为例如希望通过索引获取记录,那么[]
将无法工作,需要使用objectAt(index)
来获取。
查询一个记录
如果调用store.find()
方法时,第二个参数是一个数字或者字符串,Ember Data将尝试获取对应ID的记录。find()
方法将返回一个用请求的记录来履行的承诺。
var aSinglePost = this.store.find(‘post‘, 1); // => GET /posts/1
查询记录
如果传递给find
方法的第二个参数是一个对象,Ember Data会发送一个使用该对象来序列化出来的查询参数的GET
请求。这是方法返回与不加第二个参数时候一样的DS.PromiseArray
。
例如,可以查询名为Peter
的person
模型的所有记录:
var peters = this.store.find(‘person‘, { name: "Peter" }); // => GET to /persons?name=‘Peter‘
与路由的模型钩子集成
如同在指定路由的模型一节中讨论的一样,路由是负责告诉模板将渲染哪个模型。
Ember.Route
的model
钩子支持立即可用的异步值。如果model
钩子返回一个承诺,路由将等待承诺履行条件满足时才渲染模板。
这使得使用Ember Data的异步数据来编写应用变得容易。只需要通过model
钩子返回请求的记录,交个Ember来处理是否需要一个网络请求。
App.Router.map(function() { this.resource(‘posts‘); this.resource(‘post‘, { path: ‘:post_id‘ }); }); App.PostsRoute = Ember.Route.extend({ model: function() { return this.store.find(‘post‘); } }); App.PostRoute = Ember.Route.extend({ model: function(params) { return this.store.find(‘post‘, params.post_id); } });
使用记录
修改属性
一旦一条记录已经加载进来,你就可以开始修改它的属性(attributes)了。属性(attributes)和Ember.js中对象的普通属性(properties)差不多。
var tyrion = this.store.find(‘person‘, 1); // ...after the record has loaded tyrion.set(‘firstName‘, "Yollo");
你可以通过isDirty属性来判断一条记录是否被更改,且尚未保存。此外使用changedAttributes
函数还可以查看记录哪些部分被修改了,以及这些部分被修改前的值是什么。changedAttributes
返回一个对象,其键值是被改变的属性,而值是一个数组[oldValue, newValue]
。
person.get(‘isAdmin‘); //=> false person.get(‘isDirty‘); //=> false person.set(‘isAdmin‘, true); person.get(‘isDirty‘); //=> true person.changedAttributes(); //=> { isAdmin: [false, true] }
此时,可以通过save()
将改变持久化,也可以回滚做的改变。调用rollback()
会将所有changedAttributes
设置回其原来的值。
person.get(‘isDirty‘); //=> true person.changedAttributes(); //=> { isAdmin: [false, true] } person.rollback(); person.get(‘isDirty‘); //=> false person.get(‘isAdmin‘); //=> false person.changedAttributes(); //=> {}
使用Fixture
当开发客户端应用时,服务端可能还没有完成对应API的开发。使用FixtureAdapter
可以先进行Ember应用开发,然后切换到其他的适配器来使用API,并且这个切换并不需要改变应用的代码。
使用夹具适配器需要完成三步简单的配置:
- 创建一个新的、使用夹具适配器的store并将其关联到应用。
- 使用DS.Model.extend来定义模型。
- 将夹具(样本数据)关联到对应的模型类。
创建夹具适配器
只需将其定义为Ember.Application
应用的ApplicationAdapter
属性即可。
var App = Ember.Application.create(); App.ApplicationAdapter = DS.FixtureAdapter;
定义模型
这里定义一个编写Ember文档的人员的模型:
App.Documenter = DS.Model.extend({ firstName: DS.attr( ‘string‘ ), lastName: DS.attr( ‘string‘ ) });
关联夹具与模型类
关联夹具非常之简单。只需要将一个Javascript对象集合赋值给模型的FIXTURES
属性即可:
App.Documenter.FIXTURES = [ { id: 1, firstName: ‘Trek‘, lastName: ‘Glowacki‘ }, { id: 2, firstName: ‘Tom‘ , lastName: ‘Dale‘ } ]
在Route中使用:
App.DocumenterRoute = Ember.Route.extend({ model: function() { return this.store.find(‘documenter‘, 1); // returns a promise that will resolve // with the record representing Trek Glowacki } });
命名惯例
与REST Adapter不同,夹具适配器并不做任何命名惯例的猜测。如同上例中所示,如果在DS.Model.extend
中定义了一个firstName
属性,那么需要在夹具样本数据中使用firstName
来表示该属性。
更重要地是,需要确保夹具样本数据中得每一条记录都有一个唯一的标识。缺省情况下,Ember Data假定该标识为id
。如果没有重定义主键标识名,又未在记录中提供一个id
,那么夹具适配器会抛出一个错误。
连接Http服务器
如果Ember应用需要从HTTP服务器加载JSON数据,本指南将介绍如何配置Ember Data来从服务器端加载记录,不论服务器返回的数据格式是什么样子。
仓库使用了一个称为适配器,知道如何通过网络进行通信的对象。默认情况下,仓库会使用DS.RESTAdapter,一个可以与HTTP服务器通过XHR进行JSON数据交互的适配器。
本指南分为两部分。第一部分介绍适配器的默认行为,包括将从哪些URL获取记录,和期望的JSON格式。
第二部分介绍如何重新定义这些默认行为,如请求数据的URL和JSON的结构。
URL约定
REST适配器使用模型的名称来判定将要发送JSON的URL。
例如,用ID来请求一个App.Photo的记录:
App.PhotoRoute = Ember.Route.extend({ model: function(params) { return this.store.find(‘photo‘, params.photo_id); } });
REST适配器将发送一个GET请求到/photos/1。
可以在记录上执行的操作,在REST适配器中被映射到如下的URL上:
操作 |
HTTP Verb |
URL |
查询 |
GET |
/people/123 |
查询所有 |
GET |
/people |
更新 |
PUT |
/people/123 |
创建 |
POST |
/people |
删除 |
DELETE |
/people/123 |
Json约定
给定模型如下:
var attr = DS.attr, hasMany = DS.hasMany, belongsTo = DS.belongsTo; App.Post = DS.Model.extend({ title: attr(), comments: hasMany(‘comment‘), user: belongsTo(‘user‘) }); App.Comment = DS.Model.extend({ body: attr() });
Ember Data期望一个发送到/posts/1
请求将返回如下的JSON格式:
{ "post": { "id": 1, "title": "Rails is omakase", "comments": ["1", "2"], "user" : "dhh" }, "comments": [{ "id": "1", "body": "Rails is unagi" }, { "id": "2", "body": "Omakase O_o" }] }
自定义适配器
自定义REST适配器,需要定义一个DS.RESTAdapter
的子类,并将其命名为App.ApplicationAdapter
。接下来只需要重新定义它的属性和方法,就可以自定义记录如何加载和保存了。
自定义URL
URL前缀
如果JSON API并不在主机的根目录,而是在一个其他的路径下,那么需要为请求设置一个URL前缀。
例如,在使用了版本化的JSON API时,请求一个特定的person可能需要发送请求到/api/v1/people/1。
这种情况下,需要设置namespace属性为api/v1。
App.ApplicationAdapter = DS.RESTAdapter.extend({ namespace: ‘api/v1‘ });
现在请求一个ID为1
的person
就会发送请求到/api/v1/people/1
。
URL主机
如果JSON API的主机与提供Ember应用服务的主机不同,那么需要修改发送HTTP请求的主机。
注意为了使其正常工作,需要使用支持CORS的浏览器,并且服务器需要配置支持发送正确的CORS头。
修改请求对应的目标主机,需要设置host
属性:
App.ApplicationAdapter = DS.RESTAdapter.extend({ host: ‘https://api.example.com‘ });
现在请求一个ID为1
的person
就会发送请求到https://api.example.com/people/1
。
自定义HTTP头
一些API需要指定HTTP头,例如提供一个API密钥。任意键值对HTTP头可以通过RESTAdapter的headers属性来进行设置,Ember Data会将这些头设置到到每个Ajax请求中去。
例如:
App.ApplicationAdapter = DS.RESTAdapter.extend({ headers: { "API_KEY": "secret key", "ANOTHER_HEADER": "Some header value" } });
请求任意资源都会包含如下的HTTP头。
ANOTHER_HEADER: Some header value
API_KEY: secret key
处理元数据
与从仓库中返回的记录的同时,有时候需要处理一些元数据。元数据是除记录外,与特定模型或类型一同的数据。
分页是常见的一种元数据。例如一个博客拥有一次无法显示完的文章,那么就需要使用如下的查询:
this.store.findQuery("post", { limit: 10, offset: 0 });
为了获取不同页面的数据,只需要以10为增量来修改offset。到目前为止,一切都正常。现在有一个问题,就是如何知道拥有多少页数据呢?服务器需要以元数据的形式返回所有的记录数。
默认情况下,Ember Data的JSON反序列化会查找一个名为meta的键:
{ "post": { "id": 1, "title": "Progressive Enhancement is Dead", "comments": ["1", "2"], "links": { "user": "/people/tomdale" }, // ... }, "meta": { "total": 100 } }
特定类型的元数据将被设置到meta
中。可以使用store.metadataFor
来获取。
var meta = this.store.metadataFor("post");
现在meta.total可以用来计算拥有多少页文章了。
此外,通过重写extractMeta方法,可以自定义元数据抽取的方法。例如,服务器没有使用meta对象来返回元数据,而是返回了下面的格式:
{ "post": [ // ... ], "total": 100 }
那么可以这样来抽取元数据:
App.ApplicationSerializer = DS.RESTSerializer.extend({ extractMeta: function(store, type, payload) { if (payload && payload.total) { store.metaForType(type, { total: payload.total }); // sets the metadata for "post" delete payload.total; // keeps ember data from trying to parse "total" as a record } } });
自定义适配器
在Ember Data中,处理与后台数据仓库通信的逻辑是通过Adapter
来完成的。Ember Data适配器内置了一些关于REST API的假定。如果后台的实现与Ember Data假定的惯例不同,那么通过扩展缺省的适配器可能很容易的实现。
有时因为一些原因需要自定义适配器,例如需要使用下划线风格的URL,使用REST外的其他媒介来与后台通信,或者是使用本地后台.
扩展适配器在Ember Data中是一个常见的过程。Ember的立场是应该通过扩展适配器来添加不同的功能,而非添加标识。这样可以使得代码更加容易测试,更加易于理解,同时也降低了可能需要扩展适配器的用户的代码。
如果后台具有一定的一致性规则,那么可以定义一个ApplicationAdapter
。ApplicationAdapter
的优先级比缺省的适配器高,但是又会被模型特定的适配器取代。
App.ApplicationAdapter = DS.RESTAdapter.extend({ // Application specific overrides go here });
如果一个模型的后台有一些特殊的规则,那么可以定义一个模型特定的适配器,并将适配器命名为:"ModelName" + "Adapter"。
App.PostAdapter = DS.RESTAdapter.extend({ namespace: ‘api/v1‘ });
缺省情况下,Ember Data内置了一些非常有用的适配器。可以根据自己的实际情况,选择其中之一作为起点来自定义适配器。
DS.Adapter是最基础的适配器,其自身不包含任何功能。如果需要创建一个与Ember适配器有根本性区别的适配器,那么可以这里入手。
DS.FixtureAdapter是一个用来从内存中加载记录的适配器,常用于开发和测试阶段。
DS.RESTAdapter是最通用的扩展适配器。RESTAdapter
可以实现store
与HTTP服务器之间通过XHR交互JSON数据。大部分使用JSON API的Ember应用都应该使用RESTAdapter
。
DS.ActiveModelAdapter是一个RESTAdapter
的特列,用于与Rails风格的REST API协同工作。
自定义RESTAdapter
DS.RESTAdapter是Ember Data提供的一个最通用的扩展适配器。它提供了一些非常有用的,可以扩展来与非标准化的后台接口通信的钩子。
自定义端点路径
namespace
属性用来指定一个特定的url前缀。
App.ApplicationAdapter = DS.RESTAdapter.extend({ namespace: ‘api/1‘ });
App.Person
的请求将会发至/api/1/people/1
。
自定义主机路径
缺省情况下,适配器认为主机是当前域。如果希望指定一个新的域,那么可以通过设置适配器的host
属性来指定。
App.ApplicationAdapter = DS.RESTAdapter.extend({ host: ‘https://api.example.com‘ });
App.Person
的请求将会发至https://api.example.com/people/1
。
自定义路径
缺省情况下,RESTAdapter尝试将模型名进行驼峰化和复数化来作为路径名。如果这种惯例并不符合使用的后台接口,可以通过重载pathForType方法来实现。
例如,并不需要将模型名称复数化,需要采用下划线分割的模式替代驼峰命名,那么可以这样来重载pathForType方法:
App.ApplicationAdapter = DS.RESTAdapter.extend({ pathForType: function(type) { return Ember.String.underscore(type); } });
App.Person
的请求将会发至/person/1
。 App.UserProfile
的请求将会发至/user_profile/1
。
制作适配器
defaultSerializer属性可以用来指定适配器使用的序列化对象。这只在没有模型特定的序列化对象,也没有ApplicationSerializer的情况下。
在一个应用中,指定一个ApplicationSerializer比较容易。但是如果自定了一个通信的适配器,并且没有指定一个ApplicationSerializer,那么设定defaultSerializer属性,来确保Ember的行为正确性比较重要。
MyCustomAdapterAdapter = DS.RESTAdapter.extend({ defaultSerializer: ‘-default‘ });
社区适配器
如果Ember Data内置的适配器并不能很好的与使用的后台工作,可以查看社区维护的Ember Data适配器,看有不有合适的选择。可以去一下地方去查找:
常见问题
应该使用查询还是过滤条件来查询记录
这取决于想要查询多少记录和记录是否已经加载到仓库。
查询比较适合于搜索成百上千甚至百万级的记录。需要做的只是将搜索条件发送给服务器端,然后搜索匹配的记录的责任就交由服务器来处理。因为服务器返回的数据包含匹配的记录的ID,所以仓库即使之前没有加载这些记录也不会有影响;仓库发现这些记录并没有被缓存会通过ID来发送请求获取记录。
使用查询的缺点是它们无法得到自动更新,效率较低,并且要求服务器端支持希望执行的查询类型。
因为服务器端决定与查询匹配的记录,而不是仓库,查询无法自动更新。如果需要更新查询结果,需要手动的调用reload(),并等待服务器端返回。如果在客户端创建了一个新的记录,该记录不会自动在结果中显示,除非服务端已经包含了该记录,并且重新加载了查询结果。
因为仓库必须让服务器端来决定查询的结果,因此需要一个网络请求。这会让用户觉得缓慢,特别是在用于与服务器间的连接速度较慢的情况下。当需要访问服务器的话,Javascript Web应用会明显的感觉到慢。
最后,执行查询需要仓库和服务器端协同工作。在默认情况下,Ember Data将搜索条件作为HTTP请求的body发送到服务器端。如果服务器端并不支持查询的格式,那么需要修改服务器以便支持查询,或者通过自定义一个适配器来定义需要发送的查询。
过滤器是对仓库中缓存的对象执行实时的查询。一旦新的记录被加载到仓库,过滤器会自动检查记录是否匹配条件,如果匹配,会将其加入到搜索结果的数组中。如果这个数组显示在模板中,模板也会自动更新。
过滤器还会顾及新创建的还未保存的记录,以及被修改未保存的记录。如果希望记录在客户端一创建或者修改就在搜索结果中显示,那么就应该是用过滤器。
请记住,如果仓库不知道记录的存在,记录不会包含在过滤器中。通过仓库的push()方法可以确保仓库知道记录的存在。
此外也有一个制约条件,就是有多少记录可以保存在内存中进行过滤,直到出现性能问题。
最后,应该联合使用查询和过滤器来扬长避短。记住通过查询服务器返回的记录都是缓存在仓库中的。可以利用这一点来做过滤,通过查询仓库来将记录加载到仓库,然后使用一个过滤函数来匹配相同的记录。
这可以避免所有的记录都通过服务器来查询,同时也创建了一个能包含客户端创建和修改的记录的动态更新列表。
App.PostsFavoritedRoute = Ember.Route.extend({ model: function() { var store = this.store; // Create a filter for all favorited posts that will be displayed in // the template. Any favorited posts that are already in the store // will be displayed immediately; // Kick off a query to the server for all posts that // the user has favorited. As results from the query are // returned from the server, they will also begin to appear. return store.filter(‘post‘, { favorited: true }, function(post) { return post.get(‘isFavorited‘); }); } });
如何将后台创建的记录通知Ember Data?
当通过Ember Data的store.find
方法来请求一条记录时,Ember会自动将数据加载到store
中。这样Ember就避免了在之后在发起一个请求来获取已经获取到的记录。此外,加载一条记录到store
时,所有的包含该条记录的RecordArray
都会被更新(例如store.filter
或者store.all
构造的)。这就意味着所有依赖与RecordArray
的数据绑定或者计算属性都会在添加新的或者更新记录值的时候自动进行同步。
而一些应用可能希望能不通过store.find
请求记录来添加或者更新store
中得记录。为了实现这种需求,可以通过使用DS.Store
的push
,pushPayload
,或者update
方法。这对于那些有一个通道(例如SSE或者Web Socket)通知应用后台有新记录创建或者更新非常有用。
push是加载记录到Ember Data的store
的最简单方法。当使用push
时,一定要记住将JSON对象推入store
之前将其反序列化。push
一次只接受一条记录。如果希望一次加载一组记录到store
那么可以调用pushMany.
socket.on(‘message‘, function (message) { var type = store.modelFor(message.model); var serializer = store.serializerFor(type.typeKey); var record = serializer.extractSingle(store, type, message.data); store.push(message.model, record); });
pushPayload是一个store#push
方法的便利封装,它将使用模型实现了pushPayload
方法的序列化对象来反序列化有效载荷。需要注意的是这个方法并不能与JSONSerializer
一同使用,因为其并没有实现pushPayload
方法。
socket.on(‘message‘, function (message) { store.pushPayload(message.model, message.data); });
update与push
方法类似,不同的是其可以处理部分属性,而不需要覆盖整个记录的属性。这个方法对于只接收到记录改变的属性的通知的应用尤为有用。与push
方法一样,update
需要在调用之前将JSON对象反序列化。
socket.on(‘message‘, function (message) { var hash = message.data; var type = store.modelFor(message.model); var fields = Ember.get(type, ‘fields‘); fields.forEach(function(field) { var payloadField = Ember.String.underscore(field); if (field === payloadField) { return; } hash[field] = hash[payloadField]; delete hash[payloadField]; }); store.push(message.model, hash); });