ember-data-tastypie-adapter
Version:
Django Tastypie Adapter
288 lines (230 loc) • 9.71 kB
JavaScript
import DS from 'ember-data';
import Ember from 'ember';
export default DS.RESTSerializer.extend({
keyForAttribute: function(attr) {
return Ember.String.decamelize(attr);
},
keyForRelationship: function(key) {
return Ember.String.decamelize(key);
},
/**
Tastypie adapter does not support the sideloading feature
*/
extract: function(store, typeClass, payload, id, requestType) {
this.extractMeta(store, typeClass.modelName, payload);
var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
return this[specificExtract](store, typeClass, payload, id, requestType);
},
/**
`extractMeta` is used to deserialize any meta information in the
adapter payload. By default Ember Data expects meta information to
be located on the `meta` property of the payload object.
The actual nextUrl is being stored. The offset must be extracted from
the string to do a new call.
When there are remaining objects to be returned, Tastypie returns a
`next` URL that in the meta header. Whenever there are no
more objects to be returned, the `next` paramater value will be null.
Instead of calculating the next `offset` each time, we store the nextUrl
from which the offset will be extrated for the next request
@method extractMeta
@param {DS.Store} store
@param {subclass of DS.Model} typeClass
@param {Object} payload
*/
extractMeta: function(store, typeClass, payload) {
if (payload && payload.meta) {
var adapter = store.adapterFor(typeClass);
if (adapter && adapter.get('since') !== null && payload.meta[adapter.get('since')] !== undefined) {
payload.meta.since = payload.meta[adapter.get('since')];
}
store.setMetadataFor(typeClass, payload.meta);
delete payload.meta;
}
},
extractMany: function(loader, json, type, records) {
this.sideload(loader, type, json);
this.extractMeta(loader, type, json);
if (json.objects) {
var objects = json.objects, references = [];
if (records) { records = records.toArray(); }
for (var i = 0; i < objects.length; i++) {
if (records) { loader.updateId(records[i], objects[i]); }
var reference = this.extractRecordRepresentation(loader, type, objects[i]);
references.push(reference);
}
loader.populateArray(references);
}
},
// Tastypie defaults do not support sideloading
sideload: function() {
},
resourceUriToId: function (resourceUri) {
return resourceUri.split('/').reverse()[1];
},
normalizeId: function (hash) {
if (hash.resource_uri) {
hash.id = this.resourceUriToId(hash.resource_uri);
delete hash.resource_uri;
}
},
normalizeRelationships: function (type, hash) {
var payloadKey, self = this;
type.eachRelationship(function (key, relationship) {
if (this.keyForRelationship) {
payloadKey = this.keyForRelationship(key, relationship.kind);
if (key !== payloadKey) {
hash[key] = hash[payloadKey];
delete hash[payloadKey];
}
}
if (hash[key]) {
var isEmbedded = self.isEmbedded(relationship);
if (relationship.kind === 'belongsTo'){
var resourceUri = hash[key];
if (!isEmbedded) {
Ember.assert(relationship.key + " is an async relation but the related data in the response is not a URI", typeof resourceUri === "string");
}
hash[key] = self.resourceUriToId(hash[key]);
} else if (relationship.kind === 'hasMany'){
var ids = [];
hash[key].forEach(function (resourceUri){
if (!isEmbedded) {
Ember.assert(relationship.key + " is an async relation but the related data in the response is not a URI", typeof resourceUri === "string");
}
ids.push(self.resourceUriToId(resourceUri));
});
hash[key] = ids;
}
}
}, this);
},
extractArray: function(store, typeClass, payload) {
var records = [];
payload.objects.forEach((hash) => {
this.extractEmbeddedFromPayload(store, typeClass, hash);
records.push(this.normalize(typeClass, hash, typeClass.typeKey));
});
return records;
},
extractSingle: function(store, typeClass, payload, id, requestType) {
var newPayload = {};
this.extractEmbeddedFromPayload(store, typeClass, payload);
newPayload[typeClass.modelName] = payload;
return this._super(store, typeClass, newPayload, id, requestType);
},
isEmbedded: function(relationship) {
var key = relationship.key;
var attrs = Ember.get(this, 'attrs');
var config = attrs && attrs[key] ? attrs[key] : false;
if (config) {
// Per model serializer will take preference for the embedded mode
return (config.embedded === 'load' || config.embedded === 'always');
}
// Consider the resource as embedded if the relationship is not async
return !(relationship.options.async ? relationship.options.async : false);
},
isResourceUri: function(adapter, payload) {
if (typeof payload !== 'string') {
return false;
}
return true;
},
extractEmbeddedFromPayload: function(store, type, payload) {
var self = this;
type.eachRelationship(function(key, relationship) {
var attrs = Ember.get(self, 'attrs');
var config = attrs && attrs[key] ? attrs[key] : false;
if (self.isEmbedded(relationship)) {
if (relationship.kind === 'hasMany') {
self.extractEmbeddedFromHasMany(store, key, relationship, payload, config);
} else if (relationship.kind === 'belongsTo') {
self.extractEmbeddedFromBelongsTo(store, key, relationship, payload, config);
}
}
});
},
extractEmbeddedFromHasMany: function(store, key, relationship, payload, config) {
var serializer = store.serializerFor(relationship.type);
key = config.key ? config.key : this.keyForAttribute(key);
var ids = [];
if (!payload[key]) {
return;
}
payload[key].forEach((data) => {
var embeddedType = store.modelFor(relationship.type);
serializer.extractEmbeddedFromPayload(store, embeddedType, data);
data = serializer.normalize(embeddedType, data, embeddedType.modelName);
ids.push(serializer.relationshipToResourceUri(relationship, data, store));
store.push(embeddedType.modelName, data);
});
payload[key] = ids;
},
extractEmbeddedFromBelongsTo: function(store, key, relationship, payload, config) {
var serializer = store.serializerFor(relationship.type);
key = config.key ? config.key : this.keyForAttribute(key);
if (!payload[key]) {
return;
}
var data = payload[key];
// Don't try to process data if it's not data!
if (serializer.isResourceUri(store.adapterFor(relationship.type), data)) {
return;
}
var embeddedType = store.modelFor(relationship.type);
serializer.extractEmbeddedFromPayload(store, embeddedType, data);
data = serializer.normalize(embeddedType, data, embeddedType.modelName);
payload[key] = serializer.relationshipToResourceUri(relationship, data, store);
store.push(embeddedType, data);
},
relationshipToResourceUri: function (relationship, value, store){
if (!value) {
return value;
}
return store.adapterFor(relationship.type).buildURL(Ember.String.camelize(relationship.type), Ember.get(value, 'id'));
},
serializeIntoHash: function (data, type, record, options) {
Ember.merge(data, this.serialize(record, options));
},
serializeBelongsTo: function (snapshot, json, relationship) {
this._super.apply(this, arguments);
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
json[key] = this.relationshipToResourceUri(relationship, snapshot.belongsTo(relationship.key), snapshot.record.store);
},
serializeHasMany: function(snapshot, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key;
var relationshipType = snapshot.type.determineRelationshipType(relationship, this.store);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany' || relationshipType === 'manyToOne') {
if (this.isEmbedded(relationship)) {
if (snapshot.hasMany(relationship.key)) {
json[key] = snapshot.hasMany(relationship.key).map(function (embeddedSnapshot) {
var data = embeddedSnapshot.record.serialize({includeId: true});
return data;
});
} else {
json[key] = [];
}
} else {
var relationData = snapshot.hasMany(relationship.key);
// We can't deal with promises here. We need actual data
if (relationData instanceof DS.PromiseArray) {
// We need the content of the promise. Make sure it is fulfilled
if (relationData.get('isFulfilled')) {
// Use the fulfilled array
relationData = relationData.get('content');
} else {
// If the property hasn't been fulfilled then it hasn't changed.
// Fall back to the internal data. It contains enough for relationshipToResourceUri.
relationData = snapshot.hasMany(relationship.key).mapBy('id').map(function(_id) {
return {id: _id};
}) || [];
}
}
json[key] = relationData.map(function (next){
return this.relationshipToResourceUri(relationship, next, this.store);
}, this);
}
}
}
});