From 147ab06fcf97bfdb8a1151e9a1adda185e2f8b26 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 18 Nov 2015 16:48:21 +0100 Subject: [PATCH] Fix references in V3 payloads V3 API doesn't return any of the records more than 2 times. If a record is already included in the response any other occurences will be represented as a reference, ie. a hash with just an @href. Ember Data doesn't play nice with such references as it needs an id to identify a record. The code in this commit traverses payloads from V3 API and adds an id to each of the references that are present. For example a following payload: { "@href": "/build/1", "@type": "build" "id": 1, "state": "passed", "branch": { "@href": "/repo/1/branch/master", "name": "master", "lastBuild": { "@href": "/build/1" } } } Will be changed to: { "@href": "/build/1", "@type": "build" "id": 1, "state": "passed", "branch": { "@href": "/repo/1/branch/master", "name": "master", "lastBuild": { "@href": "/build/1", "id": 1 } } } In this case an "id" field was added to "branch.lastBuild" field. --- app/serializers/v3.js | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/app/serializers/v3.js b/app/serializers/v3.js index 0c12ec62..918d5933 100644 --- a/app/serializers/v3.js +++ b/app/serializers/v3.js @@ -1,6 +1,29 @@ import Ember from 'ember'; import DS from 'ember-data'; +var traverse = function(object, callback) { + if(!object) { + return; + } + + if(typeof(object) === 'object' && !Ember.isArray(object)) { + callback(object); + } + + if(Ember.isArray(object)) { + for(let item of object) { + traverse(item, callback); + } + } else if(typeof object === 'object') { + for(let key in object) { + if(object.hasOwnProperty(key)) { + let item = object[key]; + traverse(item, callback); + } + } + } +}; + export default DS.JSONSerializer.extend({ isNewSerializerAPI: true, @@ -38,6 +61,11 @@ export default DS.JSONSerializer.extend({ return attributes; }, + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + this._fixReferences(payload); + return this._super(...arguments); + }, + normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) { let documentHash = { data: null, @@ -111,5 +139,44 @@ export default DS.JSONSerializer.extend({ } else { return Ember.String.camelize(key); } + }, + + _fixReferences(payload) { + let byHref = {}, href, records; + if(payload['@type']) { + // API V3 doesn't return all of the objects in a full representation + // If an object is present in one place in the response, all of the + // other occurences will be just references of a kind - they will just + // include @href property. + // + // I don't want to identify records by href in ember-data, so here I'll + // set an id and a @type field on all of the references. + // + // First we need to group all of the items in the response by href: + traverse(payload, (item) => { + if(href = item['@href']) { + if(records = byHref[href]) { + records.push(item); + } else { + byHref[href] = [item]; + } + } + }); + + // Then we can choose a record with an id for each href and put the id + // in all of the other occurences. + for(let href in byHref) { + records = byHref[href]; + let recordWithAnId = records.find( (record) => record.id ); + if(recordWithAnId) { + for(let record of records) { + record.id = recordWithAnId.id; + //record['@type'] = recordWithAnId['@type']; + } + } + } + } + + return payload; } });