-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move HAL serializer functionality into mixin (#38)
* Move HAL serializer functionality into mixin * Fix import
- Loading branch information
1 parent
3f751ef
commit e29064b
Showing
2 changed files
with
272 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import Ember from 'ember'; | ||
|
||
// Reserved keys, per the HAL spec | ||
let halReservedKeys = ['_embedded', '_links'], | ||
reservedKeys = halReservedKeys.concat(['meta']), | ||
keys = Object.keys; | ||
|
||
const /*SINGLE_PAYLOAD_REQUEST_TYPES = [ | ||
'findRecord', | ||
'findBelongsTo', | ||
'queryRecord', | ||
'createRecord', | ||
'deleteRecord', | ||
'updateRecord' | ||
],*/ | ||
COLLECTION_PAYLOAD_REQUEST_TYPES = [ | ||
'findHasMany', | ||
'findMany', | ||
'query', | ||
'findAll' | ||
]; | ||
|
||
/** | ||
* @see ember-data/system/coerce-id | ||
* @param id | ||
* @returns {*} | ||
*/ | ||
function coerceId(id) { | ||
return id == null || id === '' ? null : id + ''; | ||
} | ||
|
||
function halToJSONAPILink(link) { | ||
let converted, | ||
linkKeys = keys(link); | ||
|
||
if (linkKeys.length === 1) { | ||
converted = link.href; | ||
} else { | ||
converted = {href: link.href, meta: {}}; | ||
linkKeys.forEach(key => { | ||
if (key !== 'href') { | ||
converted.meta[key] = link[key]; | ||
} | ||
}); | ||
} | ||
|
||
return converted; | ||
} | ||
|
||
function arrayFlatten(array) { | ||
let flattened = []; | ||
return flattened.concat.apply(flattened, array); | ||
} | ||
|
||
export default Ember.Mixin.create({ | ||
keyForRelationship(relationshipKey/*, relationshipMeta */) { | ||
return relationshipKey; | ||
}, | ||
keyForAttribute(attributeName/*, attributeMeta */) { | ||
return attributeName; | ||
}, | ||
keyForLink(relationshipKey/*, relationshipMeta */) { | ||
return relationshipKey; | ||
}, | ||
isSinglePayload(payload, requestType) { | ||
return COLLECTION_PAYLOAD_REQUEST_TYPES.indexOf(requestType) === -1; | ||
}, | ||
|
||
extractLink(link) { | ||
return link.href; | ||
}, | ||
|
||
/** | ||
* Use ember-data 1.13.5+ extractId method | ||
* @param modelClass | ||
* @param resourceHash | ||
* @returns {*} | ||
*/ | ||
extractId (modelClass, resourceHash) { | ||
var primaryKey = this.get('primaryKey'); | ||
var id = resourceHash[primaryKey]; | ||
return coerceId(id); | ||
}, | ||
|
||
extractMeta (store, requestType, payload, primaryModelClass) { | ||
const meta = payload.meta || {}, | ||
isSingle = this.isSinglePayload(payload, requestType); | ||
|
||
if (!isSingle) { | ||
keys(payload).forEach(key => { | ||
if (reservedKeys.indexOf(key) > -1) { | ||
return; | ||
} | ||
|
||
meta[key] = payload[key]; | ||
delete payload[key]; | ||
}); | ||
|
||
if (payload._links) { | ||
meta.links = this.extractLinks(primaryModelClass, payload); | ||
} | ||
} | ||
|
||
return meta; | ||
}, | ||
|
||
normalizeResponse (store, primaryModelClass, payload, id, requestType) { | ||
const isSingle = this.isSinglePayload(payload, requestType), | ||
documentHash = {}, | ||
meta = this.extractMeta(store, requestType, payload, primaryModelClass), | ||
included = []; | ||
|
||
if (meta) { | ||
documentHash.meta = meta; | ||
} | ||
|
||
if (isSingle) { | ||
documentHash.data = this.normalize(primaryModelClass, payload, included); | ||
} else { | ||
documentHash.data = []; | ||
payload._embedded = payload._embedded || {}; | ||
|
||
const normalizedEmbedded = Object.keys(payload._embedded).map(embeddedKey => | ||
payload._embedded[embeddedKey].map(embeddedPayload => | ||
this.normalize(primaryModelClass, embeddedPayload, included))); | ||
|
||
documentHash.data = arrayFlatten(normalizedEmbedded); | ||
} | ||
|
||
documentHash.included = included; | ||
return documentHash; | ||
}, | ||
|
||
normalize(primaryModelClass, payload, included) { | ||
let data; | ||
|
||
if (payload) { | ||
const attributes = this.extractAttributes(primaryModelClass, payload), | ||
relationships = this.extractRelationships(primaryModelClass, payload, included); | ||
|
||
data = { | ||
id: this.extractId(primaryModelClass, payload), | ||
type: primaryModelClass.modelName | ||
}; | ||
if (Object.keys(attributes).length > 0) { | ||
data.attributes = attributes; | ||
} | ||
if (Object.keys(relationships).length > 0) { | ||
data.relationships = relationships; | ||
} | ||
|
||
if (data.attributes) { | ||
this.applyTransforms(primaryModelClass, data.attributes); | ||
} | ||
} | ||
|
||
return data; | ||
}, | ||
|
||
extractLinks(primaryModelClass, payload) { | ||
let links; | ||
|
||
if (payload._links) { | ||
links = {}; | ||
Object.keys(payload._links).forEach(link => { | ||
links[link] = halToJSONAPILink(payload._links[link]); | ||
}); | ||
} | ||
|
||
return links; | ||
}, | ||
|
||
extractAttributes(primaryModelClass, payload) { | ||
let payloadKey, | ||
attributes = {}; | ||
|
||
primaryModelClass.eachAttribute((attributeName, attributeMeta)=> { | ||
payloadKey = this.keyForAttribute(attributeName, attributeMeta); | ||
|
||
if (!payload.hasOwnProperty(payloadKey)) { | ||
return; | ||
} | ||
|
||
attributes[attributeName] = payload[payloadKey]; | ||
delete payload[payloadKey]; | ||
}); | ||
|
||
if(payload._links) { | ||
attributes.links = this.extractLinks(primaryModelClass, payload); | ||
} | ||
|
||
return attributes; | ||
}, | ||
|
||
extractRelationship(relationshipModelClass, payload, included) { | ||
if (Ember.isNone(payload)) { | ||
return undefined; | ||
} | ||
|
||
let relationshipModelName = relationshipModelClass.modelName, | ||
relationship; | ||
|
||
if (Ember.typeOf(payload) === 'object') { | ||
relationship = { | ||
id: coerceId(this.extractId({}, payload)) | ||
}; | ||
|
||
if (relationshipModelName) { | ||
relationship.type = this.modelNameFromPayloadKey(relationshipModelName); | ||
included.push(this.normalize(relationshipModelClass, payload, included)); | ||
} | ||
} else { | ||
relationship = { | ||
id: coerceId(payload), | ||
type: relationshipModelName | ||
}; | ||
} | ||
|
||
return relationship; | ||
}, | ||
|
||
extractRelationships(primaryModelClass, payload, included) { | ||
let relationships = {}, | ||
embedded = payload._embedded, | ||
keyForRelationship = this.keyForRelationship, | ||
keyForLink = this.keyForLink, | ||
extractLink = this.extractLink, | ||
links = payload._links; | ||
|
||
if (embedded || links) { | ||
primaryModelClass.eachRelationship((key, relationshipMeta) => { | ||
let relationship, | ||
relationshipKey = keyForRelationship(key, relationshipMeta), | ||
linkKey = keyForLink(key, relationshipMeta); | ||
|
||
if (embedded && embedded.hasOwnProperty(relationshipKey)) { | ||
let data, | ||
relationModelClass = this.store.modelFor(relationshipMeta.type); | ||
|
||
if (relationshipMeta.kind === 'belongsTo') { | ||
data = this.extractRelationship(relationModelClass, embedded[relationshipKey], included); | ||
} else if (relationshipMeta.kind === 'hasMany') { | ||
data = embedded[relationshipKey].map(item => { | ||
return this.extractRelationship(relationModelClass, item, included); | ||
}); | ||
} | ||
|
||
relationship = {data}; | ||
} | ||
|
||
if (links && links.hasOwnProperty(linkKey)) { | ||
relationship = relationship || {}; | ||
|
||
const link = links[linkKey], | ||
useRelated = !relationship.data; | ||
|
||
relationship.links = { | ||
[useRelated ? 'related' : 'self']: extractLink(link) | ||
}; | ||
} | ||
|
||
if (relationship) { | ||
relationships[key] = relationship; | ||
} | ||
}, this); | ||
} | ||
|
||
return relationships; | ||
} | ||
}); |
Oops, something went wrong.