英文原文:http://emberjs.com/guides/models/the-rest-adapter/

REST适配器

默认情况下,store使用DS.RESTAdapter来加载和保存记录。REST适配器假定URLs和每个对象对应的JSON都符合惯例。也就是说,只要遵从规则,就可以在不对适配器进行任何配置和编写任何代码的情况下使用适配器。

URL惯例

REST适配器通过模型的名称来生成需要进行通信的URLs。例如,如下代码展示了如何通过ID来获取对应的Post

1
var post = store.find('post', 1);

REST适配器会自动发送一个GET请求到/posts/1

针对记录的操作在REST适配器里被映射为如下的URLs:

ActionHTTP VerbURL
FindGET/people/123
Find AllGET/people
UpdatePUT/people/123
CreatePOST/people
DeleteDELETE/people/123

自定义复数形态

不规则或不可数名词复数可以通过Ember.Inflector.inflector来指定:

1
2
Ember.Inflector.inflector.irregular('formula', 'formulae');
Ember.Inflector.inflector.uncountable('advice');

如上的配置下,REST适配器会采用/formulae/1来获取App.Formula,而不是使用/formulae/1

自定义端点路径

端点路径可以有一个命名空间前缀,该前缀通过适配器的namespace属性来进行配置:

1
2
3
DS.RESTAdapter.reopen({
  namespace: 'api/1'
});

现在App.Person的请求就会变为/api/1/people/1

自定义宿主

适配器可以通过设置host属性来指向其他的宿主。

1
2
3
DS.RESTAdapter.reopen({
  host: 'https://api.example.com'
});

现在App.Person的请求就会变为https://api.example.com/people/1

JSON惯例

当请求一个记录时,REST适配器期望服务器返回的记录的JSON表示遵从如下惯例。

JSON根

返回的主记录必须拥有一个命名的根。例如,如果从/people/123请求一个记录,那么响应就应该被嵌套在person属性下。

1
2
3
4
5
6
{
  "person": {
    "firstName": "Jeff",
    "lastName": "Atwood"
  }
}

属性名称

属性命名应该采用驼峰命名法。例如,假如有如下的模型:

1
2
3
4
5
6
App.Person = DS.Model.extend({
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),

  isPersonOfTheYear: DS.attr('boolean')
});

那么从服务器返回的JSON格式应该为:

1
2
3
4
5
6
7
{
  "person": {
    "firstName": "Barack",
    "lastName": "Obama",
    "isPersonOfTheYear": true
  }
}

不规则的keys可以通过自定义序列化来完成映射。如果返回的JSON包含一个名为lastNameOfPersonkey,而期望的属性名称为lastName,那么创建一个自定义的序列化类并重写normalizeHash属性。

1
2
3
4
5
6
7
8
9
10
11
12
App.Person = DS.Model.extend({
  lastName: DS.attr('string')
});
App.PersonSerializer = DS.RESTSerializer.extend({
  normalizeHash: {
    lastNameOfPerson: function(hash) {
      hash.lastName = hash.lastNameOfPerson;
      delete hash.lastNameOfPerson;
      return hash;
    }
  }
});

关联

对其他对象的引用应该通过ID来进行关联。例如,如果有一个模型拥有一个hasMany的关联:

1
2
3
App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', { async: true })
});

JSON应该将该关联编码成一个ID的数组:

1
2
3
4
5
{
  "post": {
    "comments": [1, 2, 3]
  }
}

一篇文章的评论可以通过post.get('comments')来进行获取。REST适配器将发送一个GET请求至/comments?ids[]=1&ids[]=2&ids[]=3

而JSON表示中的任何belongsTo关联,应该为以驼峰命名的Ember Data模型名称加上Id后缀,例如,有如下模型:

1
2
3
App.Comment = DS.Model.extend({
  post: DS.belongsTo('post')
});

JSON应该将关联编码为另外一个记录的ID:

1
2
3
4
5
{
  "comment": {
    "post": 1
  }
}

如果需要遵从这样的命名惯例,可以通过重写keyForRelationship方法来实现。

1
2
3
4
5
 App.ApplicationSerializer = DS.RESTSerializer.extend({
   keyForRelationship: function(key, relationship) {
      return key + 'Ids';
   }
 });

旁路加载关联

为了减少HTTP请求的次数,可以从把JSON响应中的附加对象进行旁路加载。旁路加载的记录在JSON根之外,并作为一个哈希数组存在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "post": {
    "id": 1,
    "title": "Node is not omakase",
    "comments": [1, 2, 3]
  },

  "comments": [{
    "id": 1,
    "body": "But is it _lightweight_ omakase?"
  },
  {
    "id": 2,
    "body": "I for one welcome our new omakase overlords"
  },
  {
    "id": 3,
    "body": "Put me on the fast track to a delicious dinner"
  }]
}

创建自定义变换

在一些环境中,内建的属性类型stringnumberbooleandate可能不够准确。例如,服务可能返回一个非标准的日期格式。

RESTAdapter可以注册像属性一样使用的新的转换,就如同其他Ember适配器。

1
2
3
4
5
6
7
8
9
10
11
App.CoordinatePointTransform = DS.Transform.extend({
  serialize: function(value) {
    return [value.get('x'), value.get('y')];
  },
  deserialize: function(value) {
    return Ember.create({ x: value[0], y: value[1] });
  }
});
App.Cursor = DS.Model.extend({
  position: DS.attr('coordinatePoint')
});

当从API接收到coordinatePoint时,coordinatePoint应该是一个数组:

1
2
3
4
5
{
  cursor: {
    position: [4,9]
  }
}

但是当一个对象实例一旦被加载,coordinatePoint则变成了一个对象:

1
2
3
var cursor = App.Cursor.find(1);
cursor.get('position.x'); //=> 4
cursor.get('position.y'); //=> 9

如果position被修改和保存,那么它会被传递给serialize,将其转换为JSON中的数组。