API Docs for: 0.25.0
Show:

File: addon/serializers/osf-serializer.js

import Ember from 'ember';
import DS from 'ember-data';

/**
 * @module ember-osf
 * @submodule serializers
 */

/**
 * Base serializer class for all OSF APIv2 endpoints. Provides custom behaviors for embeds, relationships, and pagination.
 * @class OsfSerializer
 * @extends DS.JSONAPISerializer
 */
export default DS.JSONAPISerializer.extend({
    attrs: {
        links: {
            serialize: false
        },
        embeds: {
            serialize: false
        }
    },

    // Map from relationship field name to type. Override to serialize relationships.
    relationshipTypes: {},

    /**
     * Extract information about records embedded inside this request
     * @method _extractEmbeds
     * @param {Object} resourceHash
     * @return {Array}
     * @private
     */
    _extractEmbeds(resourceHash) {
        if (!resourceHash.embeds) {
            return []; // Nothing to do
        }
        let included = [];
        resourceHash.relationships = resourceHash.relationships || {};
        for (let embedded in resourceHash.embeds) {
            if (!(embedded || resourceHash.embeds[embedded])) {
                continue;
            }
            //TODO Pagination probably breaks here
            let data = resourceHash.embeds[embedded].data || resourceHash.embeds[embedded];
            if (!('errors' in data)) {
                this.store.pushPayload({ data });
            }
            if (Array.isArray(data)) {
                included = included.concat(data);
            } else {
                included.push(data);
            }
            resourceHash.embeds[embedded].type = embedded;
            // Merges links returned from embedded object with relationship links, so all returned links are available.
            var embeddedLinks = resourceHash.embeds[embedded].links || {};
            resourceHash.embeds[embedded].links = Object.assign(embeddedLinks, resourceHash.relationships[embedded].links);
            resourceHash.relationships[embedded] = resourceHash.embeds[embedded];
            resourceHash.relationships[embedded].is_embedded = true;
        }
        delete resourceHash.embeds;
        //Recurse in, includeds are only processed on the top level. Embeds are nested.
        return included.concat(included.reduce((acc, include) => acc.concat(this._extractEmbeds(include)), []));
    },

    _mergeFields(resourceHash) {
        // ApiV2 `links` exist outside the attributes field; make them accessible to the data model
        if (resourceHash.links) { // TODO: Should also test whether model class defines a links field
            resourceHash.attributes.links = resourceHash.links;
        }
        this._extractEmbeds(resourceHash);

        if (resourceHash.relationships && resourceHash.attributes.links) {
            resourceHash.attributes.links = Ember.$.extend(resourceHash.attributes.links, {
                relationships: resourceHash.relationships
            });
        }
        return resourceHash;
    },

    extractAttributes(modelClass, resourceHash) {
        resourceHash = this._mergeFields(resourceHash);
        return this._super(modelClass, resourceHash);
    },

    keyForAttribute(key) {
        return Ember.String.underscore(key);
    },

    keyForRelationship(key) {
        return Ember.String.underscore(key);
    },

    serialize: function(snapshot, options) {
        var serialized = this._super(snapshot, options);
        serialized.data.type = Ember.String.underscore(serialized.data.type);
        // Only send dirty attributes in request
        if (!snapshot.record.get('isNew')) {
            for (var attribute in serialized.data.attributes) {
                if (!(Ember.String.camelize(attribute) in snapshot.record.changedAttributes())) {
                    delete serialized.data.attributes[attribute];
                }
            }
        }

        // Only serialize dirty, whitelisted relationships
        serialized.data.relationships = {};
        for (const relationship in snapshot.record._dirtyRelationships) {
            // https://stackoverflow.com/questions/29004314/why-are-object-keys-and-for-in-different
            if (!snapshot.record._dirtyRelationships.hasOwnProperty(relationship)) continue;
            const type = this.get('relationshipTypes')[relationship];
            if (type) {
                const changeLists = Object.values(snapshot.record._dirtyRelationships[relationship]);
                if (changeLists.any(l => l.length)) {
                    serialized.data.relationships[Ember.String.underscore(relationship)] = {
                        data: {
                            id: snapshot.belongsTo(relationship, { id: true }),
                            type
                        }
                    };
                }
            }
        }
        return serialized;
    },

    serializeAttribute(snapshot, json, key) {
        // In certain cases, a field may be omitted from the server payload, but have a value (undefined)
        // when serialized from the model. (eg node.template_from)
        // Omit fields with a value of undefined before sending to the server. (but still allow null to be sent)
        let val = snapshot.attr(key);
        if (val !== undefined) {
            this._super(...arguments);
        }
    },

    normalizeArrayResponse() {
        let documentHash = this._super(...arguments);
        if (documentHash.meta !== undefined && documentHash.meta.total !== undefined && documentHash.meta.per_page !== undefined) {
            // For any request that returns more than one result, calculate total pages to be loaded.
            documentHash.meta.total_pages = Math.ceil(documentHash.meta.total / documentHash.meta.per_page);
        }
        return documentHash;
    }
});