balderdashy/waterline

View on GitHub
lib/waterline/utils/ontology/is-capable-of-optimized-populate.js

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * Module dependencies
 */

var util = require('util');
var assert = require('assert');
var _ = require('@sailshq/lodash');
var getModel = require('./get-model');
var getAttribute = require('./get-attribute');


/**
 * isCapableOfOptimizedPopulate()
 *
 * Determine whether this association fully supports optimized populate.
 *
 * > Note that, if this is a plural association (a `collection` assoc. that is pointed at
 * > by `via` on the other side, or for which there IS no "other side"), then there will be
 * > a junction model in play.  For this utility to return `true`, that junction model must
 * > also be on the same datastore!
 *
 * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * @param {String} attrName        [the name of the association in question]
 * @param {String} modelIdentity   [the identity of the model this association belongs to]
 * @param {Ref} orm                [the Waterline ORM instance]
 * ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * @returns {Boolean}
 */

module.exports = function isCapableOfOptimizedPopulate(attrName, modelIdentity, orm) {

  if (!_.isString(attrName)) {
    throw new Error('Consistency violation: Must specify `attrName` as a string.  But instead, got: '+util.inspect(attrName, {depth:5})+'');
  }
  if (!_.isString(modelIdentity)) {
    throw new Error('Consistency violation: Must specify `modelIdentity` as a string.  But instead, got: '+util.inspect(modelIdentity, {depth:5})+'');
  }
  if (_.isUndefined(orm)) {
    throw new Error('Consistency violation: Must pass in `orm` (a reference to the Waterline ORM instance).  But instead, got: '+util.inspect(orm, {depth:5})+'');
  }


  //  ╦  ╔═╗╔═╗╦╔═  ╦ ╦╔═╗  ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌   ┬   ┌┬┐┌─┐┌┬┐┌─┐┬  ┌─┐
  //  ║  ║ ║║ ║╠╩╗  ║ ║╠═╝  ├─┤└─┐└─┐│ ││  │├─┤ │ ││ ││││  ┌┼─  ││││ │ ││├┤ │  └─┐
  //  ╩═╝╚═╝╚═╝╩ ╩  ╚═╝╩    ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘  └┘   ┴ ┴└─┘─┴┘└─┘┴─┘└─┘

  // Look up the containing model for this association, and the attribute definition itself.
  var PrimaryWLModel = getModel(modelIdentity, orm);
  var attrDef = getAttribute(attrName, modelIdentity, orm);

  assert(attrDef.model || attrDef.collection, 'Attempting to check whether attribute `'+attrName+'` of model `'+modelIdentity+'` is capable of optimized populate, but it\'s not even an association!');

  // Look up the other, associated model.
  var otherModelIdentity = attrDef.model ? attrDef.model : attrDef.collection;
  var OtherWLModel = getModel(otherModelIdentity, orm);



  //  ┌─┐┬ ┬┌─┐┌─┐┬┌─  ┬ ┬┬ ┬┌─┐┌┬┐┬ ┬┌─┐┬─┐  ╔═╗╦  ╦    ┌┬┐┌─┐┌┬┐┌─┐┬  ┌─┐
  //  │  ├─┤├┤ │  ├┴┐  │││├─┤├┤  │ ├─┤├┤ ├┬┘  ╠═╣║  ║    ││││ │ ││├┤ │  └─┐
  //  └─┘┴ ┴└─┘└─┘┴ ┴  └┴┘┴ ┴└─┘ ┴ ┴ ┴└─┘┴└─  ╩ ╩╩═╝╩═╝  ┴ ┴└─┘─┴┘└─┘┴─┘└─┘
  //  ┌─┐┬─┐┌─┐  ┬ ┬┌─┐┬┌┐┌┌─┐  ┌┬┐┬ ┬┌─┐  ╔═╗╔═╗╔╦╗╔═╗  ╔╦╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗╔═╗
  //  ├─┤├┬┘├┤   │ │└─┐│││││ ┬   │ ├─┤├┤   ╚═╗╠═╣║║║║╣    ║║╠═╣ ║ ╠═╣╚═╗ ║ ║ ║╠╦╝║╣
  //  ┴ ┴┴└─└─┘  └─┘└─┘┴┘└┘└─┘   ┴ ┴ ┴└─┘  ╚═╝╩ ╩╩ ╩╚═╝  ═╩╝╩ ╩ ╩ ╩ ╩╚═╝ ╩ ╚═╝╩╚═╚═╝

  // Determine if the two models are using the same datastore.
  var isUsingSameDatastore = (PrimaryWLModel.datastore === OtherWLModel.datastore);

  // Sanity check
  if (!_.isString(PrimaryWLModel.datastore) || !_.isString(OtherWLModel.datastore)) {
    throw new Error('Consistency violation: Outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012)  The `datastore` property should be a string, not an array or whatever else.  But for either the `'+PrimaryWLModel.identity+'` or `'+OtherWLModel.identity+'` model, it is not!');
  }



  // Now figure out if this association is using a junction (aka "many to many"),
  // and if so, which model it is.
  // > If it is not using a junction, we'll leave `JunctionWLModel` as undefined.
  // ------
  var JunctionWLModel;
  // To accomplish this, we'll grab the already-mapped relationship info (attached by wl-schema
  // to models, as the `schema` property).  If our directly-related model (as mapped by WL-schema
  // has a `junctionTable` flag or a `throughTable` dictionary, then we can safely say this association
  // is using a junction, and that this directly-related model is indeed that junction.
  var junctionOrOtherModelIdentity = PrimaryWLModel.schema[attrName].referenceIdentity;
  var JunctionOrOtherWLModel = getModel(junctionOrOtherModelIdentity, orm);
  var arcaneProto = Object.getPrototypeOf(JunctionOrOtherWLModel);
  if (_.isBoolean(arcaneProto.junctionTable) || _.isPlainObject(arcaneProto.throughTable)) {
    JunctionWLModel = JunctionOrOtherWLModel;
  }//>-
  // -----

  // If there is a junction, make sure to factor that in too.
  // (It has to be using the same datastore as the other two for it to count.)
  if (JunctionWLModel) {
    isUsingSameDatastore = isUsingSameDatastore && (JunctionWLModel.datastore === PrimaryWLModel.datastore);

    // Sanity check
    if (!_.isString(JunctionWLModel.datastore)) {
      throw new Error('Consistency violation: Outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012)  The `datastore` property should be a string, not an array or whatever else.  But for the `'+JunctionWLModel.identity+'` model, it is not!');
    }

  }//>-

  // Now, if any of the models involved is using a different datastore, then bail.
  if (!isUsingSameDatastore) {
    return false;
  }//-•


  // --•
  // IWMIH, we know that this association is using exactly ONE datastore.
  // And we even know that datastore's name.
  //
  // (remember, we just checked to verify that they're exactly the same above-- so we could have grabbed
  // this datastore name from ANY of the involved models)
  var relevantDatastoreName = PrimaryWLModel.datastore;

  // Sanity check
  if (!_.isString(PrimaryWLModel.datastore)) {
    throw new Error('Consistency violation: Outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012)  The `datastore` property should be a string, not an array or whatever else.  But for the `'+PrimaryWLModel.identity+'` model, it is not!');
  }

  // Another sanity check
  assert(_.isString(relevantDatastoreName));


  // Finally, now that we know which datastore we're dealing with, check to see if that datastore's
  // configured adapter supports optimized populates.
  var doesDatastoreSupportOptimizedPopulates = PrimaryWLModel._adapter.join;

  // If not, then we're done.
  if (!doesDatastoreSupportOptimizedPopulates) {
    return false;
  }//-•

  // IWMIH, then we know that all involved models in this query share a datastore, and that the datastore's
  // adapter supports optimized populates.  So we return true!
  return true;

};


// Quick test:
/*```
require('./lib/waterline/utils/ontology/is-capable-of-optimized-populate')('pets', 'user', { collections: { user: { attributes: { id: { type: 'string', required: true, unique: true }, age: { type: 'number', required: false }, foo: { type: 'string', required: true }, pets: { collection: 'pet' } }, primaryKey: 'id', hasSchema: true}, pet: { attributes: { id: { type:'number', required: true, unique: true } }, primaryKey: 'id', hasSchema: true } } });
```*/