英文原版: https://guides.emberjs.com/v2.14.0/models/relationships/
Ember Data内建了若干关系类型帮你定义当前的模型与其他模型之间的联系。
一对一
要声明两个模型之间是一对一关系,使用 DS.belongsTo:
app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
profile: DS.belongsTo('profile')
});
app/models/profile.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user')
});
一对多
要声明两个模型之间是一对多关系,结合使用DS.belongsTo和DS.hasMany:
app/models/blog-post.js
import DS from 'ember-data';
export default DS.Model.extend({
comments: DS.hasMany('comment')
});
app/models/comment.js
import DS from 'ember-data';
export default DS.Model.extend({
blogPost: DS.belongsTo('blog-post')
});
多对多
要声明两个模型之间为 多对多关系,使用DS.hasMany:
app/models/blog-post.js
import DS from 'ember-data';
export default DS.Model.extend({
tags: DS.hasMany('tag')
});
app/models/tag.js
import DS from 'ember-data';
export default DS.Model.extend({
blogPosts: DS.hasMany('blog-post')
});
显式反转
Ember Data将尽力发现那些映射到彼此的关系。 在上面的一对多代码中,例如,Ember Data可以知道,更改注释关系应该更新博客关系,因为blogPost是该模型的唯一关系。
但是,有时你可能会有相同类型的多个belongsTo / hasManys。 您可以使用DS.belongsTo或DS.hasMany的反向选项指定相关模型上的哪个属性为反向。 不带反向的关系可以通过设置{inverse:null}来表示。
app/models/comment.js
import DS from 'ember-data';
export default DS.Model.extend({
onePost: DS.belongsTo('blog-post', { inverse: null }),
twoPost: DS.belongsTo('blog-post'),
redPost: DS.belongsTo('blog-post'),
bluePost: DS.belongsTo('blog-post')
});
app/models/blog-post.js
import DS from 'ember-data';
export default DS.Model.extend({
comments: DS.hasMany('comment', {
inverse: 'redPost'
})
});
自反关系
当你要定义一个自反关系时(模型与它本身建立关系),你必须显示的定义反向关系。如果不存在自反关系你需要设置{inverse:null}。
下例是一个一对多的自反关系:
app/models/folder.js
import DS from 'ember-data';
export default DS.Model.extend({
children: DS.hasMany('folder', { inverse: 'parent' }),
parent: DS.belongsTo('folder', { inverse: 'children' })
});
下面的例子是一个一对一的自反关系:
app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
bestFriend: DS.belongsTo('user', { inverse: 'bestFriend' }),
});
你也可以定义一个不带反向联系的自反关系:
app/models/folder.js
import DS from 'ember-data';
export default DS.Model.extend({
parent: DS.belongsTo('folder', { inverse: null })
});
多态性
多态是一个非常重要的概念,它使得开发者可以把公共的功能抽取到一个基类中。考虑下面的例子:一个用户有多种支付方式。他有个PayPal账户,和一堆信用卡。
注意,为了使多态起作用,Ember Data期望通过一个保留属性polymorphic声明”type”是多态的。难以理解?看下面例子:
首先,看一下模型的定义:
app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
paymentMethods: DS.hasMany('payment-method', { polymorphic: true })
});
app/models/payment-method.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user', { inverse: 'paymentMethods' }),
});
app/models/payment-method-cc.js
import PaymentMethod from './payment-method';
import Ember from 'ember';
export default PaymentMethod.extend({
obfuscatedIdentifier: Ember.computed('last4', function () {
return `**** **** **** ${this.get('last4')}`;
})
});
import PaymentMethod from './payment-method'
import DS from 'ember-data';
import Ember from 'ember';
export default PaymentMethod.extend({
linkedEmail: DS.attr(),
obfuscatedIdentifier: Ember.computed('linkedEmail', function () {
let last5 = this.get('linkedEmail').split('').reverse().slice(0, 5).reverse().join('');
return `••••${last5}`;
})
});
我们接口会如下设置关联关系:
{
"data": {
"id": "8675309",
"type": "user",
"attributes": {
"name": "Anfanie Farmeo"
},
"relationships": {
"payment-methods": {
"data": [{
"id": "1",
"type": "PaymentMethodPaypal" }, {
"id": "2",
"type": "PaymentMethodCc" }, {
"id": "3",
"type": "PaymentMethodApplePay" }]
}
}
},
"included": [{
"id": "1",
"type": "PaymentMethodPaypal",
"attributes": {
"linked-email": "ryan@gosling.io"
}
}, {
"id": "2",
"type": "PaymentMethodCc",
"attributes": {
"last4": "1335"
}
}, {
"id": "3",
"type": "PaymentMethodApplePay",
"attributes": {
"last4": "5513"
}
}]
}
只读的嵌套数据
一些模型有可能带有一些深度嵌套的只读的属性。比较容易想到的解决方法是为每一层嵌套的对象使用hasMany和belongsTo来创建带有关联关系的模型对象。然而,由于只读数据不需要被更新和保存,所以这往往会导致创建的大量代码,而实际上的收益是非常小的。一个比较好的实现方式是用一个未定义转换器的属性来定义关联关系。这使得通过计算属性和模板来访问只读数据时避免了定义外部模型的开销。
创建记录
假设现在我们有个blog-post和一个comment模型,它们彼此间存在联系:
app/models/blog-post.js
import DS from 'ember-data';
export default DS.Model.extend({
comments: DS.hasMany('comment')
});
app/models/comment.js
import DS from 'ember-data';
export default DS.Model.extend({
blogPost: DS.belongsTo('blog-post')
});
当用户在博客上发表了一条评论,我们需要建立这两条记录之间的联系。我们可以通过belongsTo关系在为新创建的comment找到一个宿主blogPost:
let blogPost = this.get('store').peekRecord('blog-post', 1);
let comment = this.get('store').createRecord('comment', {
blogPost: blogPost
});
comment.save();
这将会创建一个新的comment记录并且保存到服务器。同时Ember Data也需要借助blogPost对于comments的关系来更新blogPost的相关数据。
在此,我们通过hasMany关系在blogPost上增加一条寄生的评论:
let blogPost = this.get('store').peekRecord('blog-post', 1);
let comment = this.get('store').createRecord('comment', {
});
blogPost.get('comments').pushObject(comment);
comment.save().then(function () {
blogPost.save();
});
在上述情况下,这个新创建的comment从属关系将会被定向到blogPost上。
虽然createRecord比较简单,但是需要注意的是你不能为promise指派关联关系。
例子,如果你希望为blogPost设置一个author属性,在user对象被存储进store之前,这是不能得到正确的结果的:
this.get('store').createRecord('blog-post', {
title: 'Rails is Omakase',
body: 'Lorem ipsum',
author: this.get('store').findRecord('user', 1)
});
然而,你可以在promise返回之后再来设置这个关系:
let blogPost = this.get('store').createRecord('blog-post', {
title: 'Rails is Omakase',
body: 'Lorem ipsum'
});
this.get('store').findRecord('user', 1).then(function(user) {
blogPost.set('author', user);
});
检索相关的记录
当你从服务器请求的模型数据包含多个关联关系时,你或许会想要检索到与关联关系对应的记录。比如,当你检索一篇博客的时候,你也需要检索这篇博客下面对应的评论。 JSON API specification规定服务器通过接收一个带有键值include的请求参数来返回包含相关记录的响应。参数的值应该是一串被逗号分割的被关联的模型的名称列表。
如果你用的是支持JSON API的适配器,比如Ember默认的JSONAPIAdapter,你可以通过findRecord(), findAll(), query() 和queryRecord()方法轻松的发起请求。
findRecord() 和findAll()都可以带一个options参数,通过这个参数你可以指定include参数。例子,给定一个带有hasMany关系的post模型,该关系指向一个comment模型,当检索指定的post时同时可以要求服务器返回相关的comments:
app/routes/post.js
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, {include: 'comments'});
}
});
随后,对应于此post的comments就可以通过model.comments被使用了。
嵌套的关联关系可以通过点分形式来获取。所以,获取与post相关的comments以及这些comments的作者可以通过以下形式:
app/routes/post.js
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('post', params.post_id, {include: 'comments,comments.author'});
}
});
query()和queryRecord()可以带一个查询参数对象,此参数对象直接被序列化进URL中:
app/routes/adele.js
export default Ember.Route.extend({
model() {
// GET to /artists?filter[name]=Adele&include=albums
this.store.query('artist', {
filter: {name: 'Adele'},
include: 'albums'
}).then(function(artists) {
return artists.get('firstObject');
});
}
});
更新记录
有时候我们需要在已有的记录上设置关联关系。我们可以如此简单的设置belongsTo关系:
let blogPost = this.get('store').peekRecord('blog-post', 1);
let comment = this.get('store').peekRecord('comment', 1);
comment.set('blogPost', blogPost);
comment.save();
另外,我们可以通过将一条记录推入post来更新hasMany关系的相关数据:
let blogPost = this.get('store').peekRecord('blog-post', 1);
let comment = this.get('store').peekRecord('comment', 1);
blogPost.get('comments').pushObject(comment);
blogPost.save();
删除关联关系
要删除belongsTo关联关系,只需要设置null值,而且会自动从hasMany关系中剔除该comment:
let comment = this.get('store').peekRecord('comment', 1);
comment.set('blogPost', null);
comment.save();
当然,删除hasMany关联关系可以如下操作,也会从belongsTo关系中剔除该blogPost:
let blogPost = this.get('store').peekRecord('blog-post', 1);
let comment = this.get('store').peekRecord('comment', 1);
blogPost.get('comments').removeObject(comment);
blogPost.save();
作为promise的关联关系
当处理关联关系的时候,请记住它们会返回promise对象。
例子,如果我们要在blogPost上异步的处理comments,我们不得不等到知道promise被返回:
let blogPost = this.get('store').peekRecord('blog-post', 1);
blogPost.get('comments').then((comments) => {
// now we can work with the comments
});
对于belongsTo也是一样的:
let comment = this.get('store').peekRecord('comment', 1);
comment.get('blogPost').then((blogPost) => {
// the blogPost is available here
});
Handlerbars模板将会自动的从promise中更新带回来的数据。我们可以这样显示blogPost中的那些comments:
<ul>
{{#each blogPost.comments as |comment|}}
<li>{{comment.id}}</li>
{{/each}}
</ul>
Ember Data将会从服务器查询响应的记录,并且在收到数据后重新渲染模板。
本节完
———– PS—————
自反关系的部分翻译的不准确