UNPKG

7.48 kBJavaScriptView Raw
1import DS from 'ember-data';
2import Ember from 'ember';
3
4/**
5 @module ember-data
6 */
7
8const {
9 singularize,
10 classify,
11 decamelize,
12 camelize,
13 underscore
14} = Ember.String;
15
16const {
17 RESTSerializer,
18 normalizeModelName
19} = DS;
20
21/**
22 The ActiveModelSerializer is a subclass of the RESTSerializer designed to integrate
23 with a JSON API that uses an underscored naming convention instead of camelCasing.
24 It has been designed to work out of the box with the
25 [active\_model\_serializers](http://github.com/rails-api/active_model_serializers)
26 Ruby gem. This Serializer expects specific settings using ActiveModel::Serializers,
27 `embed :ids, embed_in_root: true` which sideloads the records.
28
29 This serializer extends the DS.RESTSerializer by making consistent
30 use of the camelization, decamelization and pluralization methods to
31 normalize the serialized JSON into a format that is compatible with
32 a conventional Rails backend and Ember Data.
33
34 ## JSON Structure
35
36 The ActiveModelSerializer expects the JSON returned from your server
37 to follow the REST adapter conventions substituting underscored keys
38 for camelcased ones.
39
40 ### Conventional Names
41
42 Attribute names in your JSON payload should be the underscored versions of
43 the attributes in your Ember.js models.
44
45 For example, if you have a `Person` model:
46
47 ```js
48 App.FamousPerson = DS.Model.extend({
49 firstName: DS.attr('string'),
50 lastName: DS.attr('string'),
51 occupation: DS.attr('string')
52 });
53 ```
54
55 The JSON returned should look like this:
56
57 ```js
58 {
59 "famous_person": {
60 "id": 1,
61 "first_name": "Barack",
62 "last_name": "Obama",
63 "occupation": "President"
64 }
65 }
66 ```
67
68 Let's imagine that `Occupation` is just another model:
69
70 ```js
71 App.Person = DS.Model.extend({
72 firstName: DS.attr('string'),
73 lastName: DS.attr('string'),
74 occupation: DS.belongsTo('occupation')
75 });
76
77 App.Occupation = DS.Model.extend({
78 name: DS.attr('string'),
79 salary: DS.attr('number'),
80 people: DS.hasMany('person')
81 });
82 ```
83
84 The JSON needed to avoid extra server calls, should look like this:
85
86 ```js
87 {
88 "people": [{
89 "id": 1,
90 "first_name": "Barack",
91 "last_name": "Obama",
92 "occupation_id": 1
93 }],
94
95 "occupations": [{
96 "id": 1,
97 "name": "President",
98 "salary": 100000,
99 "person_ids": [1]
100 }]
101 }
102 ```
103
104 @class ActiveModelSerializer
105 @namespace DS
106 @extends DS.RESTSerializer
107*/
108var ActiveModelSerializer = RESTSerializer.extend({
109 // SERIALIZE
110
111 /**
112 Converts camelCased attributes to underscored when serializing.
113
114 @method keyForAttribute
115 @param {String} attribute
116 @return String
117 */
118 keyForAttribute: function(attr) {
119 return decamelize(attr);
120 },
121
122 /**
123 Underscores relationship names and appends "_id" or "_ids" when serializing
124 relationship keys.
125
126 @method keyForRelationship
127 @param {String} relationshipModelName
128 @param {String} kind
129 @return String
130 */
131 keyForRelationship: function(relationshipModelName, kind) {
132 var key = decamelize(relationshipModelName);
133 if (kind === "belongsTo") {
134 return key + "_id";
135 } else if (kind === "hasMany") {
136 return singularize(key) + "_ids";
137 } else {
138 return key;
139 }
140 },
141
142 /**
143 `keyForLink` can be used to define a custom key when deserializing link
144 properties. The `ActiveModelSerializer` camelizes link keys by default.
145
146 @method keyForLink
147 @param {String} key
148 @param {String} kind `belongsTo` or `hasMany`
149 @return {String} normalized key
150 */
151 keyForLink: function(key, relationshipKind) {
152 return camelize(key);
153 },
154
155 /*
156 Does not serialize hasMany relationships by default.
157 */
158 serializeHasMany: function() {},
159
160 /**
161 Underscores the JSON root keys when serializing.
162
163 @method payloadKeyFromModelName
164 @param {String} modelName
165 @return {String}
166 */
167 payloadKeyFromModelName: function(modelName) {
168 return underscore(decamelize(modelName));
169 },
170
171 /**
172 Serializes a polymorphic type as a fully capitalized model name.
173
174 @method serializePolymorphicType
175 @param {DS.Snapshot} snapshot
176 @param {Object} json
177 @param {Object} relationship
178 */
179 serializePolymorphicType: function(snapshot, json, relationship) {
180 var key = relationship.key;
181 var belongsTo = snapshot.belongsTo(key);
182 var jsonKey = underscore(key + "_type");
183
184 if (Ember.isNone(belongsTo)) {
185 json[jsonKey] = null;
186 } else {
187 json[jsonKey] = classify(belongsTo.modelName).replace('/', '::');
188 }
189 },
190
191 /**
192 Add extra step to `DS.RESTSerializer.normalize` so links are normalized.
193
194 If your payload looks like:
195
196 ```js
197 {
198 "post": {
199 "id": 1,
200 "title": "Rails is omakase",
201 "links": { "flagged_comments": "api/comments/flagged" }
202 }
203 }
204 ```
205
206 The normalized version would look like this
207
208 ```js
209 {
210 "post": {
211 "id": 1,
212 "title": "Rails is omakase",
213 "links": { "flaggedComments": "api/comments/flagged" }
214 }
215 }
216 ```
217
218 @method normalize
219 @param {subclass of DS.Model} typeClass
220 @param {Object} hash
221 @param {String} prop
222 @return Object
223 */
224 normalize: function(typeClass, hash, prop) {
225 this.normalizeLinks(hash);
226 return this._super(typeClass, hash, prop);
227 },
228
229 /**
230 Convert `snake_cased` links to `camelCase`
231
232 @method normalizeLinks
233 @param {Object} data
234 */
235
236 normalizeLinks: function(data) {
237 if (data.links) {
238 var links = data.links;
239
240 for (var link in links) {
241 var camelizedLink = camelize(link);
242
243 if (camelizedLink !== link) {
244 links[camelizedLink] = links[link];
245 delete links[link];
246 }
247 }
248 }
249 },
250
251 extractRelationships: function(modelClass, resourceHash) {
252 modelClass.eachRelationship(function (key, relationshipMeta) {
253 var relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, "deserialize");
254
255 // prefer the format the AMS gem expects, e.g.:
256 // relationship: {id: id, type: type}
257 if (relationshipMeta.options.polymorphic) {
258 extractPolymorphicRelationships(key, relationshipMeta, resourceHash, relationshipKey);
259 }
260 // If the preferred format is not found, use {relationship_name_id, relationship_name_type}
261 if (resourceHash.hasOwnProperty(relationshipKey) && typeof resourceHash[relationshipKey] !== 'object') {
262 var polymorphicTypeKey = this.keyForRelationship(key) + '_type';
263 if (resourceHash[polymorphicTypeKey] && relationshipMeta.options.polymorphic) {
264 let id = resourceHash[relationshipKey];
265 let type = resourceHash[polymorphicTypeKey];
266 delete resourceHash[polymorphicTypeKey];
267 delete resourceHash[relationshipKey];
268 resourceHash[relationshipKey] = { id: id, type: type };
269 }
270 }
271 },this);
272 return this._super.apply(this, arguments);
273 },
274
275 modelNameFromPayloadKey: function(key) {
276 var convertedFromRubyModule = singularize(key.replace('::', '/'));
277 return normalizeModelName(convertedFromRubyModule);
278 }
279});
280
281function extractPolymorphicRelationships(key, relationshipMeta, resourceHash, relationshipKey) {
282 let polymorphicKey = decamelize(key);
283 let hash = resourceHash[polymorphicKey];
284 if (hash !== null && typeof hash === 'object') {
285 resourceHash[relationshipKey] = hash;
286 }
287}
288
289export default ActiveModelSerializer;