UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

577 lines (480 loc) 31.7 kB
/** * Module Dependencies */ var util = require('util'); var async = require('async'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var parley = require('parley'); var buildOmen = require('../utils/query/build-omen'); var forgeAdapterError = require('../utils/query/forge-adapter-error'); var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); var forgeStageThreeQuery = require('../utils/query/forge-stage-three-query'); var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); var processAllRecords = require('../utils/query/process-all-records'); var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); /** * Module constants */ var DEFERRED_METHODS = getQueryModifierMethods('destroy'); /** * destroy() * * Destroy records that match the specified criteria. * * ``` * // Destroy all bank accounts with more than $32,000 in them. * BankAccount.destroy().where({ * balance: { '>': 32000 } * }).exec(function(err) { * // ... * }); * ``` * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * * Usage without deferred object: * ================================================ * * @param {Dictionary?} criteria * * @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?} meta * @qkey {String} using * @qkey {String} method * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ module.exports = function destroy(/* criteria, explicitCbMaybe, metaContainer */) { // 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 callback below. var omen = buildOmen(destroy); // Build initial query. var query = { method: 'destroy', using: modelIdentity, criteria: undefined, meta: undefined }; // ██╗ ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗ ██████╗███████╗ // ██║ ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║██╔════╝██╔════╝ // ██║ ██║███████║██████╔╝██║███████║██║ ██║██║██║ ███████╗ // ╚██╗ ██╔╝██╔══██║██╔══██╗██║██╔══██║██║ ██║██║██║ ╚════██║ // ╚████╔╝ ██║ ██║██║ ██║██║██║ ██║██████╔╝██║╚██████╗███████║ // ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═════╝╚══════╝ // // FUTURE: when time allows, update this to match the "VARIADICS" format // used in the other model methods. // The explicit callback, if one was provided. var explicitCbMaybe; // Handle double meaning of first argument: // // • destroy(criteria, ...) if (!_.isFunction(arguments[0])) { query.criteria = arguments[0]; explicitCbMaybe = arguments[1]; query.meta = arguments[2]; } // • destroy(explicitCbMaybe, ...) else { explicitCbMaybe = arguments[0]; query.meta = arguments[1]; } // ██████╗ ███████╗███████╗███████╗██████╗ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ // ██║ ██║█████╗ █████╗ █████╗ ██████╔╝ // ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗ // ██████╔╝███████╗██║ ███████╗██║ ██║ // ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ // // ██╗███╗ ███╗ █████╗ ██╗ ██╗██████╗ ███████╗██╗ // ██╔╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝╚██╗ // ██║ ██╔████╔██║███████║ ╚████╔╝ ██████╔╝█████╗ ██║ // ██║ ██║╚██╔╝██║██╔══██║ ╚██╔╝ ██╔══██╗██╔══╝ ██║ // ╚██╗██║ ╚═╝ ██║██║ ██║ ██║ ██████╔╝███████╗██╔╝ // ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ // // ┌┐ ┬ ┬┬┬ ┌┬┐ ┬ ┬─┐┌─┐┌┬┐┬ ┬┬─┐┌┐┌ ┌┐┌┌─┐┬ ┬ ┌┬┐┌─┐┌─┐┌─┐┬─┐┬─┐┌─┐┌┬┐ // ├┴┐│ │││ ││ ┌┼─ ├┬┘├┤ │ │ │├┬┘│││ │││├┤ │││ ││├┤ ├┤ ├┤ ├┬┘├┬┘├┤ ││ // └─┘└─┘┴┴─┘─┴┘ └┘ ┴└─└─┘ ┴ └─┘┴└─┘└┘ ┘└┘└─┘└┴┘ ─┴┘└─┘└ └─┘┴└─┴└─└─┘─┴┘ // ┌─ ┬┌─┐ ┬─┐┌─┐┬ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ─┐ // │─── │├┤ ├┬┘├┤ │ ├┤ └┐┌┘├─┤│││ │ ───│ // └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ // If a callback function was not specified, then build a new Deferred and bail now. // // > This method will be called AGAIN automatically when the Deferred is executed. // > and next time, it'll have a callback. return parley( function (done){ // Otherwise, IWMIH, we know that a callback was specified. // So... // ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ // ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ // █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ // ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ // ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ // ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ // // ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ // // Forge a stage 2 query (aka logical protostatement) // This ensures a normalized format. 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_NOOP': // Determine the appropriate no-op result. // If `fetch` meta key is set, use `[]`-- otherwise use `undefined`. // // > Note that future versions might simulate output from the raw driver. // > (e.g. `{ numRecordsDestroyed: 0 }`) // > See: https://github.com/treelinehq/waterline-query-docs/blob/master/docs/results.md#destroy var noopResult = undefined; if (query.meta && query.meta.fetch) { noopResult = []; }//>- return done(undefined, noopResult); default: return done(e); } } // ╦ ╦╔═╗╔╗╔╔╦╗╦ ╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ // ╠═╣╠═╣║║║ ║║║ ║╣ BEFORE │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ // ╩ ╩╩ ╩╝╚╝═╩╝╩═╝╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ // Determine what to do about running any lifecycle callback. (function _runBeforeLC(proceed) { // If the `skipAllLifecycleCallbacks` meta flag was set, don't run the lifecycle callback. if (query.meta && query.meta.skipAllLifecycleCallbacks) { return proceed(undefined, query); } // If there is no relevant LC, then just proceed. if (!_.has(WLModel._callbacks, 'beforeDestroy')) { return proceed(undefined, query); } // But otherwise, run it. WLModel._callbacks.beforeDestroy(query.criteria, function (err){ if (err) { return proceed(err); } return proceed(undefined, query); }); })(function _afterRunningBeforeLC(err, query) { if (err) { return done(err); } // ┬ ┌─┐┌─┐┬┌─┬ ┬┌─┐ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ // │ │ ││ │├┴┐│ │├─┘ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ // ┴─┘└─┘└─┘┴ ┴└─┘┴ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ // Look up the appropriate adapter to use for this model. // Get a reference to the adapter. var adapter = WLModel._adapter; if (!adapter) { // ^^One last sanity check to make sure the adapter exists-- again, for compatibility's sake. return done(new Error('Consistency violation: Cannot find adapter for model (`' + modelIdentity + '`). This model appears to be using datastore `'+WLModel.datastore+'`, but the adapter for that datastore cannot be located.')); } // Verify the adapter has a `destroy` method. if (!adapter.destroy) { return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `destroy` method.')); } // If `cascade` is enabled, do an extra assertion... if (query.meta && query.meta.cascade){ // First, a sanity check to ensure the adapter has a `find` method too. if (!adapter.find) { return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `find` method, but that method is mandatory to be able to use `cascade: true`.')); } }//>- // ================================================================================ // 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); // ================================================================================ // ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ // Now, destructively forge this S2Q into a S3Q. try { query = forgeStageThreeQuery({ stageTwoQuery: query, identity: modelIdentity, transformer: WLModel._transformer, originalModels: orm.collections }); } catch (e) { return done(e); } // ┬┌─┐ ╔═╗╔═╗╔═╗╔═╗╔═╗╔╦╗╔═╗ ┌─┐┌┐┌┌─┐┌┐ ┬ ┌─┐┌┬┐ ┌┬┐┬ ┬┌─┐┌┐┌ // │├┤ ║ ╠═╣╚═╗║ ╠═╣ ║║║╣ ├┤ │││├─┤├┴┐│ ├┤ ││ │ ├─┤├┤ │││ // ┴└ ╚═╝╩ ╩╚═╝╚═╝╩ ╩═╩╝╚═╝ └─┘┘└┘┴ ┴└─┘┴─┘└─┘─┴┘┘ ┴ ┴ ┴└─┘┘└┘ // ┌─┐┬┌┐┌┌┬┐ ╦╔╦╗╔═╗ ┌┬┐┌─┐ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ // ├┤ ││││ ││ ║ ║║╚═╗ │ │ │ ││├┤ └─┐ │ ├┬┘│ │└┬┘ // └ ┴┘└┘─┴┘ ╩═╩╝╚═╝ ┴ └─┘ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ (function _maybeFindIdsToDestroy(proceed) { // If `cascade` meta key is NOT enabled, then just proceed. if (!query.meta || !query.meta.cascade) { return proceed(); } // Look up the ids of records that will be destroyed. // (We need these because, later, since `cascade` is enabled, we'll need // to empty out all of their associated collections.) // // > FUTURE: instead of doing this, consider forcing `fetch: true` in the // > implementation of `.destroy()` when `cascade` meta key is enabled (mainly // > for consistency w/ the approach used in createEach()/create()) // To do this, we'll grab the appropriate adapter method and call it with a stage 3 // "find" query, using almost exactly the same QKs as in the incoming "destroy". // The only tangible difference is that its criteria has a `select` clause so that // records only contain the primary key field (by column name, of course.) var pkColumnName = WLModel.schema[WLModel.primaryKey].columnName; if (!pkColumnName) { return done(new Error('Consistency violation: model `' + WLModel.identity + '` schema has no primary key column name!')); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // > Note: We have to look up the column name this way (instead of simply using the // > getAttribute() utility) because it is currently only fully normalized on the // > `schema` dictionary-- the model's attributes don't necessarily have valid, // > normalized column names. For more context, see: // > https://github.com/balderdashy/waterline/commit/19889b7ee265e9850657ec2b4c7f3012f213a0ae#commitcomment-20668097 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - adapter.find(WLModel.datastore, { method: 'find', using: query.using, criteria: { where: query.criteria.where, skip: query.criteria.skip, limit: query.criteria.limit, sort: query.criteria.sort, select: [ pkColumnName ] }, meta: query.meta //<< this is how we know that the same db connection will be used }, function _afterPotentiallyFindingIdsToDestroy(err, pRecords) { if (err) { err = forgeAdapterError(err, omen, 'find', modelIdentity, orm); return proceed(err); } // Slurp out just the array of ids (pk values), and send that back. var ids = _.pluck(pRecords, pkColumnName); return proceed(undefined, ids); });//</adapter.find()> })(function _afterPotentiallyLookingUpRecordsToCascade(err, idsOfRecordsBeingDestroyedMaybe) { if (err) { return done(err); } // Now we'll actually perform the `destroy`. // ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ // └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ // └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ // Call the `destroy` adapter method. adapter.destroy(WLModel.datastore, query, function _afterTalkingToAdapter(err, rawAdapterResult) { if (err) { err = forgeAdapterError(err, omen, 'destroy', modelIdentity, orm); return done(err); }//-• // ╦═╗╔═╗╦╔╗╔ ╔╦╗╔═╗╦ ╦╔╗╔ ╔╦╗╔═╗╔═╗╔╦╗╦═╗╦ ╦╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌┐┌┌┬┐┌─┐ // ╠╦╝╠═╣║║║║ ║║║ ║║║║║║║ ║║║╣ ╚═╗ ║ ╠╦╝║ ║║ ║ ║║ ║║║║ │ ││││ │ │ │ // ╩╚═╩ ╩╩╝╚╝ ═╩╝╚═╝╚╩╝╝╚╝ ═╩╝╚═╝╚═╝ ╩ ╩╚═╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └─┘┘└┘ ┴ └─┘ // ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ ┌─ ┬ ┌─┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ ─┐ // ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ │ │ ├┤ │ ├─┤└─┐│ ├─┤ ││├┤ │ // ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ └─ ┴o└─┘o └─┘┴ ┴└─┘└─┘┴ ┴─┴┘└─┘ ─┘ (function _maybeWipeAssociatedCollections(proceed) { // If `cascade` meta key is NOT enabled, then just proceed. if (!query.meta || !query.meta.cascade) { return proceed(); } // Otherwise, then we should have the records we looked up before. // (Here we do a quick sanity check.) if (!_.isArray(idsOfRecordsBeingDestroyedMaybe)) { return proceed(new Error('Consistency violation: Should have an array of records looked up before! But instead, got: '+util.inspect(idsOfRecordsBeingDestroyedMaybe, {depth: 5})+'')); } // --• // Now we'll clear out collections belonging to the specified records. // (i.e. use `replaceCollection` to wipe them all out to be `[]`) // First, if there are no target records, then gracefully bail without complaint. // (i.e. this is a no-op) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Revisit this and verify that it's unnecessary. While this isn't a bad micro-optimization, // its existence makes it seem like this wouldn't work or would cause a warning or something. And it // really shouldn't be necessary. (It's doubtful that it adds any real tangible performance benefit anyway.) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (idsOfRecordsBeingDestroyedMaybe.length === 0) { return proceed(); }//-• // Otherwise, we have work to do. // // Run .replaceCollection() for each associated collection of the targets, wiping them all out. // (if n..m, this destroys junction records; otherwise, it's n..1, so this just nulls out the other side) // // > Note that we pass through `meta` here, ensuring that the same db connection is used, if possible. async.each(_.keys(WLModel.attributes), function _eachAttribute(attrName, next) { var attrDef = WLModel.attributes[attrName]; // Skip everything other than collection attributes. if (!attrDef.collection){ return next(); } // But otherwise, this is a collection attribute. So wipe it. WLModel.replaceCollection(idsOfRecordsBeingDestroyedMaybe, attrName, [], function (err) { if (err) { if (err.name === 'PropagationError') { return next(flaverr({ name: err.name, code: err.code, message: 'Failed to run the "cascade" polyfill. Could not propagate the potential '+ 'destruction of '+(idsOfRecordsBeingDestroyedMaybe.length===1?'this '+WLModel.identity+' record':('these '+idsOfRecordsBeingDestroyedMaybe.length+' '+WLModel.identity+' records'))+'.\n'+ 'Details:\n'+ ' '+err.message+'\n'+ '\n'+ 'This error originated from the fact that the "cascade" polyfill was enabled for this query.\n'+ 'Tip: Try reordering your .destroy() calls.\n'+ ' [?] See https://sailsjs.com/support for more help.\n' }, omen)); }//• else { return next(err); } }//• return next(); }, query.meta);//</.replaceCollection()> },// ~∞%° function _afterwards(err) { if (err) { return proceed(err); } return proceed(); });//</ async.each > })(function _afterPotentiallyWipingCollections(err) {// ~∞%° if (err) { return done(err); } // ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┐ ┌┐ ┬ ┬┌┬┐ ┌─┐┌┐┌┬ ┬ ┬ ┬┌─┐ // ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├┬┘├┤ │ │ │├┬┘ ││└─┐ ├┴┐│ │ │ │ │││││ └┬┘ │├┤ // ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴└─└─┘└─┘└─┘┴└──┴┘└─┘ooo└─┘└─┘ ┴ └─┘┘└┘┴─┘┴ ┴└ // ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ // ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ // ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ (function _maybeTransformRecords(proceed){ // If `fetch` was not enabled, return. if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { // > Note: This `if` statement is a convenience, for cases where the result from // > the adapter may have been coerced from `undefined` to `null` automatically. // > (we want it to be `undefined` still, for consistency) if (_.isNull(rawAdapterResult)) { return proceed(); }//-• if (!_.isUndefined(rawAdapterResult)) { console.warn('\n'+ 'Warning: Unexpected behavior in database adapter:\n'+ 'Since `fetch` is NOT enabled, this adapter (for datastore `'+WLModel.datastore+'`)\n'+ 'should NOT have sent back anything as the 2nd argument when triggering the callback\n'+ 'from its `destroy` method. But it did!\n'+ '\n'+ '(Displaying this warning to help avoid confusion and draw attention to the bug.\n'+ 'Specifically, got:\n'+ util.inspect(rawAdapterResult, {depth:5})+'\n'+ '(Ignoring it and proceeding anyway...)'+'\n' ); }//>- // Continue on. return proceed(); }//-• // IWMIH then we know that `fetch: true` meta key was set, and so the // adapter should have sent back an array. // Verify that the raw result from the adapter is an array. if (!_.isArray(rawAdapterResult)) { return proceed(new Error( 'Unexpected behavior in database adapter: Since `fetch: true` was enabled, this adapter '+ '(for datastore `'+WLModel.datastore+'`) should have sent back an array of records as the 2nd argument when triggering '+ 'the callback from its `destroy` method. But instead, got: '+util.inspect(rawAdapterResult, {depth:5})+'' )); }//-• // Attempt to convert the column names in each record back into attribute names. var transformedRecords; try { transformedRecords = rawAdapterResult.map(function(record) { return WLModel._transformer.unserialize(record); }); } catch (e) { return proceed(e); } // Check the records 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(transformedRecords, query.meta, modelIdentity, orm); } catch (e) { return proceed(e); } // Now continue on. return proceed(undefined, transformedRecords); })(function (err, transformedRecordsMaybe){ if (err) { return done(err); } // ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ // ╠═╣╠╣ ║ ║╣ ╠╦╝ ││├┤ └─┐ │ ├┬┘│ │└┬┘ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ // ╩ ╩╚ ╩ ╚═╝╩╚═ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ // Run "after" lifecycle callback AGAIN and AGAIN- once for each record. // ============================================================ async.each(transformedRecordsMaybe, function _eachRecord(record, next) { // If the `skipAllLifecycleCallbacks` meta flag was set, don't run any of // the methods. if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { return next(); } // Skip "after" lifecycle callback, if not defined. if (!_.has(WLModel._callbacks, 'afterDestroy')) { return next(); } // Otherwise run it. WLModel._callbacks.afterDestroy(record, function _afterMaybeRunningAfterDestroyForThisRecord(err) { if (err) { return next(err); } return next(); }); },// ~∞%° function _afterIteratingOverRecords(err) { if (err) { return done(err); } return done(undefined, transformedRecordsMaybe); });//_∏_ (†: async.each() -- ran "after" lifecycle callback on each record) });//_∏_ (†: after determining (and potentially transforming) the result from the adapter) });//_∏_ (†: _afterPotentiallyWipingCollections) });//_∏_ (adapter.destroy) }); //_∏_ (†: after potentially looking up records to cascade) }); //_∏_ (†: "before" LC) }, 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> };