travis-web/app/serializers/v3.js
Piotr Sarnacki 147ab06fcf 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.
2015-12-08 10:18:06 +01:00

183 lines
4.9 KiB
JavaScript

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,
extractRelationship(type, hash) {
let relationshipHash = this._super(...arguments);
if(relationshipHash && relationshipHash['@type']) {
relationshipHash.type = relationshipHash['@type'];
} else if(relationshipHash && !relationshipHash.type) {
relationshipHash.type = type;
}
return relationshipHash;
},
extractRelationships() {
let relationships = this._super(...arguments);
return relationships;
},
keyForRelationship(key, typeClass, method) {
if(key && key.underscore) {
return key.underscore();
} else {
return key;
}
},
extractAttributes(modelClass, resourceHash) {
let attributes = this._super(...arguments);
for(let key in attributes) {
if(key.startsWith('@')) {
delete attributes.key;
}
}
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,
included: []
};
let meta = this.extractMeta(store, primaryModelClass, payload);
if (meta) {
Ember.assert('The `meta` returned from `extractMeta` has to be an object, not "' + Ember.typeOf(meta) + '".', Ember.typeOf(meta) === 'object');
documentHash.meta = meta;
}
let items, type;
if(type = payload['@type']) {
items = payload[type];
} else {
items = payload[primaryModelClass.modelName.underscore() + 's'];
}
documentHash.data = items.map((item) => {
let { data, included } = this.normalize(primaryModelClass, item);
if (included) {
documentHash.included.push(...included);
}
return data;
});
return documentHash;
},
normalize(modelClass, resourceHash) {
let { data, included } = this._super(...arguments);
if(!included) {
included = [];
}
let store = this.store;
if(data.relationships) {
Object.keys(data.relationships).forEach(function (key) {
let relationship = data.relationships[key];
let process = function(data) {
if(data['@representation'] !== 'standard') {
return;
}
let type = data['@type'];
let serializer = store.serializerFor(type);
let modelClass = store.modelFor(type);
let normalized = serializer.normalize(modelClass, data);
included.push(normalized.data);
if(normalized.included) {
normalized.included.forEach(function(item) {
included.push(item);
});
}
};
if(Array.isArray(relationship.data)) {
relationship.data.forEach(process);
} else if(relationship && relationship.data) {
process(relationship.data);
}
});
}
return { data, included };
},
keyForAttribute(key, method) {
if(method === 'deserialize') {
return Ember.String.underscore(key);
} 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;
}
});