UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

314 lines (268 loc) 15.5 kB
/** * Module Dependencies */ var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var parley = require('parley'); var buildOmen = require('../utils/query/build-omen'); var getModel = require('../utils/ontology/get-model'); var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); /** * Module constants */ var DEFERRED_METHODS = getQueryModifierMethods('archive'); /** * archive() * * Archive (s.k.a. "soft-delete") records that match the specified criteria, * saving them as new records in the built-in Archive model, then destroying * the originals. * * ``` * // Archive all bank accounts with more than $32,000 in them. * BankAccount.archive().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 archive(/* 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(archive); // Build initial query. var query = { method: 'archive', 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: // // • archive(criteria, ...) if (!_.isFunction(arguments[0])) { query.criteria = arguments[0]; explicitCbMaybe = arguments[1]; query.meta = arguments[2]; } // • archive(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 (err) { switch (err.code) { case 'E_INVALID_CRITERIA': return done( flaverr({ name: 'UsageError', code: err.code, details: err.details, message: 'Invalid criteria.\n'+ 'Details:\n'+ ' '+err.details+'\n' }, omen) ); case 'E_NOOP': // Determine the appropriate no-op result. // If `fetch` meta key is set, use `[]`-- otherwise use `undefined`. var noopResult = undefined; if (query.meta && query.meta.fetch) { noopResult = []; }//>- return done(undefined, noopResult); default: return done(err); } }//fi // Bail now if archiving has been disabled. if (!WLModel.archiveModelIdentity) { return done(flaverr({ name: 'UsageError', message: 'Since the `archiveModelIdentity` setting was explicitly disabled, .archive() cannot be used.' }, omen)); }//• // Look up the Archive model. var Archive = WLModel.archiveModelIdentity; try { Archive = getModel(WLModel.archiveModelIdentity, orm); } catch (err) { return done(err); }//fi // - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: pass through the `omen` in the metadata. // - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Maybe refactor this into more-generic `.move()` and/or // `.copy()` methods for migrating data between models/datastores. // Then just leverage those methods here in `.archive()`. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬┌┐┌┌┬┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ ├┤ ││││ ││ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └ ┴┘└┘─┴┘ └─┘└└─┘└─┘┴└─ ┴ // Note that we pass in `meta` here, as well as in the other queries // below. (This ensures we're on the same db connection, provided one // was explicitly passed in!) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // WARNING: // // Before proceeding with calling an additional model method that relies // on criteria other than the primary .destroy(), we'll want to back up a // copy of our s2q's criteria (`query.criteria`). // // This is important because, in an effort to improve performance, // Waterline methods destructively mutate criteria when forging queries // for use in the adapter(s). Since we'll be reusing criteria, we need // to insulate ourselves from those destructive changes in case there are // custom column names involved. (e.g. Mongo's `_id``) // // > While the criteria might contain big crazy stuff for comparing with // > type:ref attributes, a deep clone is the best option we have. // // FUTURE: in s2q forge logic, for "archive" method, reject with an error // if deep refs (non-JSON-serializable data) are discovered in criteria. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var s2qCriteriaForFind = _.cloneDeep(query.criteria); WLModel.find(s2qCriteriaForFind, function _afterFinding(err, foundRecords) { if (err) { return done(err); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: as an optimization, fetch records batch-at-a-time // using .stream() instead of just doing a naïve `.find()`. // (This would allow you to potentially archive millions of records // at a time without overflowing RAM.) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var archives = []; _.each(foundRecords, function(record){ archives.push({ originalRecord: record, originalRecordId: record[WLModel.primaryKey], fromModel: WLModel.identity, }); });//∞ // ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐┌─┐┌─┐┌─┐┬ ┬ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ │ ├┬┘├┤ ├─┤ │ ├┤ ├┤ ├─┤│ ├─┤ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘└─┘┴ ┴└─┘┴ ┴ └─┘└└─┘└─┘┴└─ ┴ Archive.createEach(archives, function _afterCreatingEach(err) { if (err) { return done(err); } // Remove the `limit`, `skip`, `sort`, `select`, and `omit` clauses so // that our `destroy` query is valid. // (This is because they were automatically attached above in the forging.) delete query.criteria.limit; delete query.criteria.skip; delete query.criteria.sort; delete query.criteria.select; delete query.criteria.omit; // ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ // ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ ││├┤ └─┐ │ ├┬┘│ │└┬┘ │─┼┐│ │├┤ ├┬┘└┬┘ // ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ └─┘└└─┘└─┘┴└─ ┴ WLModel.destroy(query.criteria, function _afterDestroying(err) { if (err) { return done(err); } if (query.meta&&query.meta.fetch){ return done(undefined, foundRecords); } else { return done(); } }, query.meta);//</.destroy()> }, query.meta);//</.createEach()> }, query.meta);//</.find()> }, 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> };