UNPKG

offshore

Version:
111 lines (89 loc) 3.49 kB
/** * Module dependencies */ var _ = require('lodash'); /** * Traverse the schema to build a populate plan object * that will populate every relation, sub-relation, and so on * reachable from the initial model and relation at least once * (perhaps most notable is that this provides access to most * related data without getting caught in loops.) * * @param {[type]} schema [description] * @param {[type]} initialModel [description] * @param {[type]} initialRelation [description] * @return {[type]} [description] */ module.exports = function acyclicTraversal(schema, initialModel, initialRelation) { // Track the edges which have already been traversed var alreadyTraversed = [ // { // relation: initialRelation, // model: initialModel // } ]; return traverseSchemaGraph(initialModel, initialRelation); /** * Recursive function * @param {[type]} modelIdentity [description] * @param {[type]} nameOfRelation [description] * @return {[type]} [description] */ function traverseSchemaGraph(modelIdentity, nameOfRelation) { var currentModel = schema[modelIdentity]; var currentAttributes = currentModel.attributes; var isRedundant; // If this relation has already been traversed, return. // (i.e. `schema.attributes.modelIdentity.nameOfRelation`) isRedundant = _.find(alreadyTraversed, { alias: nameOfRelation, model: modelIdentity }); if (isRedundant) return; // Push this relation onto the `alreadyTraversed` stack. alreadyTraversed.push({ alias: nameOfRelation, model: modelIdentity }); var relation = currentAttributes[nameOfRelation]; if (!relation) throw new Error('Unknown relation in schema: ' + modelIdentity + '.' + nameOfRelation); var identityOfRelatedModel = relation.model || relation.collection; // Get the related model var relatedModel = schema[identityOfRelatedModel]; // If this relation is a collection with a `via` back-reference, // push it on to the `alreadyTraversed` stack. // (because the information therein is probably redundant) // TODO: evaluate this-- it may or may not be a good idea // (but I think it's a nice touch) if (relation.via) { alreadyTraversed.push({ alias: relation.via, model: identityOfRelatedModel }); } // Lookup ALL the relations OF THE RELATED model. var relations = _(relatedModel.attributes).reduce(function buildSubsetOfAssociations(relations, attrDef, attrName) { if (_.isObject(attrDef) && (attrDef.model || attrDef.collection)) { relations.push(_.merge({ alias: attrName, identity: attrDef.model || attrDef.collection, cardinality: attrDef.model ? 'model' : 'collection' }, attrDef)); return relations; } return relations; }, []); // Return a piece of the result plan by calling `traverseSchemaGraph` // on each of the RELATED model's relations. return _.reduce(relations, function(resultPlanPart, relation) { // Recursive step resultPlanPart[relation.alias] = traverseSchemaGraph(identityOfRelatedModel, relation.alias); // Trim undefined result plan parts if (resultPlanPart[relation.alias] === undefined) { delete resultPlanPart[relation.alias]; } return resultPlanPart; }, {}); } };