UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

170 lines (145 loc) 10.2 kB
/** * Module dependencies */ var util = require('util'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var getModel = require('./get-model'); /** * Module constants */ var KNOWN_ATTR_TYPES = ['string', 'number', 'boolean', 'json', 'ref']; /** * getAttribute() * * Look up an attribute definition (by name) from the specified model. * Usable with normal attributes AND with associations. * * > Note that we do a few quick assertions in the process, purely as sanity checks * > and to help prevent bugs. If any of these fail, then it means there is some * > unhandled usage error, or a bug going on elsewhere in Waterline. * * ------------------------------------------------------------------------------------------ * @param {String} attrName * The name of the attribute (e.g. "id" or "favoriteBrands") * > Useful for looking up the Waterline model and accessing its attribute definitions. * * @param {String} modelIdentity * The identity of the model this is referring to (e.g. "pet" or "user") * > Useful for looking up the Waterline model and accessing its attribute definitions. * * @param {Ref} orm * The Waterline ORM instance. * ------------------------------------------------------------------------------------------ * @returns {Ref} [the attribute definition (a direct reference to it, so be careful!!)] * ------------------------------------------------------------------------------------------ * @throws {Error} If no such model exists. * E_MODEL_NOT_REGISTERED * * @throws {Error} If no such attribute exists. * E_ATTR_NOT_REGISTERED * * @throws {Error} If anything else goes wrong. * ------------------------------------------------------------------------------------------ */ module.exports = function getAttribute(attrName, modelIdentity, orm) { // ================================================================================================ // Check that the provided `attrName` is valid. // (`modelIdentity` and `orm` will be automatically checked by calling `getModel()`) // // > Note that this attr name MIGHT be empty string -- although it should never be. // > (we prevent against that elsewhere) if (!_.isString(attrName)) { throw new Error('Consistency violation: `attrName` must be a string.'); } // ================================================================================================ // Try to look up the Waterline model. // // > Note that, in addition to being the model definition, this // > "WLModel" is actually the hydrated model object (fka a "Waterline collection") // > which has methods like `find`, `create`, etc. var WLModel = getModel(modelIdentity, orm); // Try to look up the attribute definition. var attrDef = WLModel.attributes[attrName]; if (_.isUndefined(attrDef)) { throw flaverr('E_ATTR_NOT_REGISTERED', new Error('No such attribute (`'+attrName+'`) exists in model (`'+modelIdentity+'`).')); } // ================================================================================================ // This section consists of more sanity checks for the attribute definition: if (!_.isObject(attrDef) || _.isArray(attrDef) || _.isFunction(attrDef)) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) corresponds with a CORRUPTED attribute definition: '+util.inspect(attrDef, {depth:5})+''); } // Some basic sanity checks that this is a valid model association. // (note that we don't get too deep here-- though we could) if (!_.isUndefined(attrDef.model)) { if(!_.isString(attrDef.model) || attrDef.model === '') { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an invalid `model` property. If specified, `model` should be a non-empty string. But instead, got: '+util.inspect(attrDef.model, {depth:5})+''); } if (!_.isUndefined(attrDef.via)){ throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is an association, because it declares a `model`. But with a "model" association, the `via` property should always be undefined. But instead, it is: '+util.inspect(attrDef.via, {depth:5})+''); } if (!_.isUndefined(attrDef.dominant)){ throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is an association, because it declares a `model`. But with a "model" association, the `dominant` property should always be undefined. But instead, it is: '+util.inspect(attrDef.dominant, {depth:5})+''); } try { getModel(attrDef.model, orm); } catch (e){ throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is an association, because it declares a `model`. But the other model it references (`'+attrDef.model+'`) is missing or invalid. Details: '+e.stack); } } // Some basic sanity checks that this is a valid collection association. // (note that we don't get too deep here-- though we could) else if (!_.isUndefined(attrDef.collection)) { if (!_.isString(attrDef.collection) || attrDef.collection === '') { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an invalid `collection` property. If specified, `collection` should be a non-empty string. But instead, got: '+util.inspect(attrDef.collection, {depth:5})+''); } var OtherWLModel; try { OtherWLModel = getModel(attrDef.collection, orm); } catch (e){ throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is an association, because it declares a `collection`. But the other model it references (`'+attrDef.collection+'`) is missing or invalid. Details: '+e.stack); } if (!_.isUndefined(attrDef.via)) { if (!_.isString(attrDef.via) || attrDef.via === '') { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an invalid `via` property. If specified, `via` should be a non-empty string. But instead, got: '+util.inspect(attrDef.via, {depth:5})+''); } // Note that we don't call getAttribute recursively. (That would be madness.) // We also don't check for reciprocity on the other side. // Instead, we simply do a watered down check. // > waterline-schema goes much deeper here. // > Remember, these are just sanity checks for development. if (!_.isUndefined(attrDef.through)) { var ThroughWLModel; try { ThroughWLModel = getModel(attrDef.through, orm); } catch (e){ throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is a "through" association, because it declares a `through`. But the junction model it references as "through" (`'+attrDef.through+'`) is missing or invalid. Details: '+e.stack); } if (!ThroughWLModel.attributes[attrDef.via]) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is a "through" association, because it declares a `through`. But the association\'s specified `via` ('+attrDef.via+'`) does not correspond with a recognized attribute on the junction model (`'+attrDef.through+'`)'); } if (!ThroughWLModel.attributes[attrDef.via].model) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is a "through" association, but its specified `via` ('+attrDef.via+'`) corresponds with an unexpected attribute on the junction model (`'+attrDef.through+'`). The attribute referenced by `via` should be a singular ("model") association, but instead, got: '+util.inspect(ThroughWLModel.attributes[attrDef.via],{depth: 5})+''); } } else { if (!OtherWLModel.attributes[attrDef.via]) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) is an association, because it declares a `collection`. But that association also specifies a `via` ('+attrDef.via+'`) which does not correspond with a recognized attribute on the other model (`'+attrDef.collection+'`)'); } } }//</if has `via`> }//</if has `collection`> // Otherwise, check that this is a valid, miscellaneous attribute. else { if(!_.isString(attrDef.type) || attrDef.type === '') { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an invalid `type` property. If specified, `type` should be a non-empty string. But instead, got: '+util.inspect(attrDef.type, {depth:5})+''); } if(!_.contains(KNOWN_ATTR_TYPES, attrDef.type)) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an unrecognized `type`: `'+attrDef.type+'`.'); } if (!_.isBoolean(attrDef.required)) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has an unrecognized `required` property in its definition. By this time, it should always be true or false. But instead, got: '+util.inspect(attrDef.required, {depth:5})+''); } if (attrDef.required && !_.isUndefined(attrDef.defaultsTo)) { throw new Error('Consistency violation: The referenced attribute (`'+attrName+'`, from model `'+modelIdentity+'`) has `required: true`, but it also specifies a `defaultsTo`. This should never have been allowed-- defaultsTo should be undefined! But instead, got: '+util.inspect(attrDef.defaultsTo, {depth:5})+''); } } // ================================================================================================ //-• // Send back a reference to this attribute definition. return attrDef; };