1 | import DS from 'ember-data';
|
2 | import Ember from 'ember';
|
3 |
|
4 | /**
|
5 | @module ember-data
|
6 | */
|
7 |
|
8 | const {
|
9 | singularize,
|
10 | classify,
|
11 | decamelize,
|
12 | camelize,
|
13 | underscore
|
14 | } = Ember.String;
|
15 |
|
16 | const {
|
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 | */
|
108 | var 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 |
|
281 | function 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 |
|
289 | export default ActiveModelSerializer;
|