UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

355 lines (294 loc) 17.5 kB
/** * Module dependencies */ var util = require('util'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var parley = require('parley'); var buildOmen = require('../utils/query/build-omen'); var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); var helpFind = require('../utils/query/help-find'); var processAllRecords = require('../utils/query/process-all-records'); var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); /** * Module constants */ var DEFERRED_METHODS = getQueryModifierMethods('findOne'); /** * findOne() * * Find the record matching the specified criteria. * * ``` * // Look up the bank account with exactly $34,986 in it. * BankAccount.findOne().where({ * balance: { '>': 34986 } * }).exec(function(err, bankAccount) { * // ... * }); * ``` * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * * Usage without deferred object: * ================================================ * * @param {Dictionary?} criteria * * @param {Dictionary} populates * * @param {Function?} explicitCbMaybe * Callback function to run when query has either finished successfully or errored. * (If unspecified, will return a Deferred object instead of actually doing anything.) * * @param {Ref?} meta * For internal use. * * @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * * The underlying query keys: * ============================== * * @qkey {Dictionary?} criteria * @qkey {Dictionary?} populates * * @qkey {Dictionary?} meta * @qkey {String} using * @qkey {String} method * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ module.exports = function findOne( /* criteria?, populates?, explicitCbMaybe?, meta? */ ) { // Verify `this` refers to an actual Sails/Waterline model. verifyModelMethodContext(this); // Set up a few, common local vars for convenience / familiarity. var WLModel = this; var orm = this.waterline; var modelIdentity = this.identity; // Build an omen for potential use in the asynchronous callbacks below. var omen = buildOmen(findOne); // Build query w/ initial, universal keys. var query = { method: 'findOne', using: modelIdentity }; // ██╗ ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗ ██████╗███████╗ // ██║ ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║██╔════╝██╔════╝ // ██║ ██║███████║██████╔╝██║███████║██║ ██║██║██║ ███████╗ // ╚██╗ ██╔╝██╔══██║██╔══██╗██║██╔══██║██║ ██║██║██║ ╚════██║ // ╚████╔╝ ██║ ██║██║ ██║██║██║ ██║██████╔╝██║╚██████╗███████║ // ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═════╝╚══════╝ // // The `explicitCbMaybe` callback, if one was provided. var explicitCbMaybe; // Handle the various supported usage possibilities // (locate the `explicitCbMaybe` callback, and extend the `query` dictionary) // // > Note that we define `args` so that we can insulate access // > to the arguments provided to this function. var args = arguments; (function _handleVariadicUsage() { // The metadata container, if one was provided. var _meta; // Handle first argument: // // • findOne(criteria, ...) query.criteria = args[0]; // Handle double meaning of second argument: // // • findOne(..., populates, explicitCbMaybe, _meta) var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); if (is2ndArgDictionary) { query.populates = args[1]; explicitCbMaybe = args[2]; _meta = args[3]; } // • findOne(..., explicitCbMaybe, _meta) else { explicitCbMaybe = args[1]; _meta = args[2]; } // Fold in `_meta`, if relevant. if (_meta) { query.meta = _meta; } // >- })(); // ██████╗ ███████╗███████╗███████╗██████╗ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝ // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗ // ██████╔╝███████╗██║ ███████╗██║ ██║ // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ // // ██╗███╗ ███╗ █████╗ ██╗ ██╗██████╗ ███████╗██╗ // ██╔╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝╚██╗ // ██║ ██╔████╔██║███████║ ╚████╔╝ ██████╔╝█████╗ ██║ // ██║ ██║╚██╔╝██║██╔══██║ ╚██╔╝ ██╔══██╗██╔══╝ ██║ // ╚██╗██║ ╚═╝ ██║██║ ██║ ██║ ██████╔╝███████╗██╔╝ // ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ // // ┌┐ ┬ ┬┬┬ ┌┬┐ ┬ ┬─┐┌─┐┌┬┐┬ ┬┬─┐┌┐┌ ┌┐┌┌─┐┬ ┬ ┌┬┐┌─┐┌─┐┌─┐┬─┐┬─┐┌─┐┌┬┐ // ├┴┐│ │││ ││ ┌┼─ ├┬┘├┤ │ │ │├┬┘│││ │││├┤ │││ ││├┤ ├┤ ├┤ ├┬┘├┬┘├┤ ││ // └─┘└─┘┴┴─┘─┴┘ └┘ ┴└─└─┘ ┴ └─┘┴└─┘└┘ ┘└┘└─┘└┴┘ ─┴┘└─┘└ └─┘┴└─┴└─└─┘─┴┘ // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐ // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│ // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ // If an explicit callback function was specified, then immediately run the logic below // and trigger the explicit callback when the time comes. Otherwise, build and return // a new Deferred now. (If/when the Deferred is executed, the logic below will run.) return parley( function (done){ // Otherwise, IWMIH, we know that it's time to actually do some stuff. // So... // // ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ // ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ // █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ // ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ // ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ // ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ // ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ // // Forge a stage 2 query (aka logical protostatement) try { forgeStageTwoQuery(query, orm); } catch (e) { switch (e.code) { case 'E_INVALID_CRITERIA': return done( flaverr({ name: 'UsageError', code: e.code, details: e.details, message: 'Invalid criteria.\n' + 'Details:\n' + ' ' + e.details + '\n' }, omen) ); case 'E_INVALID_POPULATES': return done( flaverr({ name: 'UsageError', code: e.code, details: e.details, message: 'Invalid populate(s).\n' + 'Details:\n' + ' ' + e.details + '\n' }, omen) ); case 'E_NOOP': return done(undefined, undefined); default: return done(e); } } // >-• // ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╔╗ ╔═╗╔═╗╔═╗╦═╗╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ // ├─┤├─┤│││ │││ ├┤ ╠╩╗║╣ ╠╣ ║ ║╠╦╝║╣ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ // ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╚═╝╚═╝╚ ╚═╝╩╚═╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ // Determine what to do about running any lifecycle callbacks (function _maybeRunBeforeLC(proceed){ // If the `skipAllLifecycleCallbacks` meta key was enabled, then don't run this LC. if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { return proceed(undefined, query); }//-• // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: This is where the `beforeFindOne()` lifecycle callback would go // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - return proceed(undefined, query); })(function _afterPotentiallyRunningBeforeLC(err, query) { if (err) { return done(err); } // ================================================================================ // FUTURE: potentially bring this back (but also would need the `omit clause`) // ================================================================================ // // Before we get to forging again, save a copy of the stage 2 query's // // `select` clause. We'll need this later on when processing the resulting // // records, and if we don't copy it now, it might be damaged by the forging. // // // // > Note that we don't need a deep clone. // // > (That's because the `select` clause is only 1 level deep.) // var s2QSelectClause = _.clone(query.criteria.select); // ================================================================================ // ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ // └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ // └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ // Use `helpFind()` to forge stage 3 quer(y/ies) and then call the appropriate adapters' method(s). // > Note: `helpFind` is responsible for running the `transformer`. // > (i.e. so that column names are transformed back into attribute names) helpFind(WLModel, query, omen, function _afterFetchingRecords(err, populatedRecords) { if (err) { return done(err); }//-• // console.log('result from operation runner:', record); // If more than one matching record was found, then consider this an error. if (populatedRecords.length > 1) { return done(flaverr({ message: 'More than one matching record found for `'+ modelIdentity[0].toUpperCase()+modelIdentity.substring(1)+ '.findOne()`:\n'+ '···\n'+ _.pluck(populatedRecords, WLModel.primaryKey)+'\n'+ '···\n'+ '\n'+ 'Criteria used:\n'+ '···\n'+ util.inspect(query.criteria, {depth:5})+'\n'+ '···' }, omen)); }//-• // Check and see if we actually found a record. var thePopulatedRecord = _.first(populatedRecords); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Allow a `mustExist: true` meta key to be specified, probably via the use of a simple new query // method-- something like `.mustExist()`. If set, then if the record is not found, bail with an error. // This is just a nicety to simplify some of the more annoyingly repetitive userland code that one needs // to write in a Node/Sails app. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // If so... if (thePopulatedRecord) { // Check the record to verify compliance with the adapter spec, // as well as any issues related to stale data that might not have been // been migrated to keep up with the logical schema (`type`, etc. in // attribute definitions). try { processAllRecords([ thePopulatedRecord ], query.meta, modelIdentity, orm); } catch (e) { return done(e); } }//>- // ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ // ├─┤├─┤│││ │││ ├┤ ╠═╣╠╣ ║ ║╣ ╠╦╝ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ // ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╩ ╩╚ ╩ ╚═╝╩╚═ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ (function _maybeRunAfterLC(proceed){ // If the `skipAllLifecycleCallbacks` meta key was enabled, then don't run this LC. if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { return proceed(undefined, thePopulatedRecord); }//-• // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: This is where the `afterFindOne()` lifecycle callback would go // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - return proceed(undefined, thePopulatedRecord); })(function _afterPotentiallyRunningAfterLC(err, thePopulatedRecord){ if (err) { return done(err); } // All done. return done(undefined, thePopulatedRecord); });//</ self-calling function to handle "after" lifecycle callback > }); //</ helpFind() > }); //</ self-calling function to handle "before" lifecycle callback > }, explicitCbMaybe, _.extend(DEFERRED_METHODS, { // Provide access to this model for use in query modifier methods. _WLModel: WLModel, // Set up initial query metadata. _wlQueryInfo: query, }) );//</parley> };