waterline
Advanced tools
Comparing version 0.13.0-parley2 to 0.13.0-rc1
@@ -141,10 +141,2 @@ # How Waterline Works | ||
// • `false` - special case, only for when this is a plural ("collection") association: when the provided subcriteria would actually be a no-op that will always end up as `[]` | ||
// | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// > Side note about what to expect under the relevant key in record(s) when you populate vs. don't populate: | ||
// > • When populating a singular association, you'll always get either a dictionary (a child record) or `null` (if no child record matches the fk; e.g. if the fk was old, or if it was `null`) | ||
// > • When populating a plural association, you'll always get an array of dictionaries (child records). Of course, it might be empty. | ||
// > • When NOT populating a singular association, you'll get whatever is stored in the database (there is no guarantee it will be correct-- if you fiddle with your database directly at the physical layer, you could mess it up). Note that we ALWAYS guarantee that the key will be present though, so long as it's not being explicitly excluded by `omit` or `select`. i.e. even if the database says it's not there, the key will exist as `null`. | ||
// > • When NOT populating a plural association, you'll never get the key. It won't exist on the resulting record(s). | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -163,3 +155,3 @@ friends: { | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// > Why don't we coallesce the "and"s above? It's kind of ugly. | ||
@@ -171,3 +163,3 @@ // | ||
// when they are built (remember: we're building these on a per-query basis). | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
] | ||
@@ -187,3 +179,30 @@ }, | ||
##### Side note about populating | ||
``` | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// > Side note about what to expect under the relevant key in record(s) when you populate vs. don't populate: | ||
// > • When populating a singular ("model") attribute, you'll always get either a dictionary (a child record) or `null` (if no child record matches the fk; e.g. if the fk was old, or if it was `null`) | ||
// > • When populating a plural ("collection") attribute, you'll always get an array of dictionaries (a collection, consisting of child records). Of course, it might be empty. | ||
// > • When NOT populating a singular ("model") attribute, you'll get whatever is stored in the database (there is no guarantee it will be correct-- if you fiddle with your database directly at the physical layer, you could mess it up). Note that we ALWAYS guarantee that the key will be present though, so long as it's not being explicitly excluded by `omit` or `select`. i.e. even if the database says it's not there, the key will exist as `null`. | ||
// > • When NOT populating a plural ("collection") attribute, you'll never get the key. It won't exist on the resulting parent record(s). | ||
// > • If populating a plural ("collection") attribute, and child records w/ duplicate ids exist in the collection (e.g. because of a corrupted physical database), any duplicate child records are stripped out. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
``` | ||
Also, some more formal terminology: | ||
+ Ideally, one uses the word "association" when one wants to refer to _both sides_ of the association *at the same time*. It's still possible to understand what it means more generally or when referring to a particular attribute, but it's one of those things that's helpful to be able to get a bit more formal about sometimes. | ||
+ When one needs to be specific, one refers to the attribute defs themselves as "singular attributes" (or more rarely: "model attribute") and "plural attribute" (aka "collection attribute"). | ||
+ one uses "singular" and "plural" to refer to a _particular side_ of the association. So really, in that parlance, an "association" is never wholly singular or plural-- it's just that the attributes on either side are. Similarly, you can't always look at a plural or singular attribute and decide whether it's part 2-way or 1-way association (you don't always have enough information) | ||
+ A 1-way (or "exclusive") association is either a vialess collection attribute, or a singular attribute that is not pointed at by a via on the other side | ||
+ A 2-way (or "shared") association is any collection attribute with `via`, or a singular attribute that _is_ pointed at by a via on the other side | ||
+ A 2-way association that is laid out in such a way that it needs a junction model to fully represent it is called a many-to-many association | ||
+ When referring to a record which might be populated, one calls it a "parent record" (or rarely: "primary record") | ||
+ Finally, when referring to a populated key/value pair within a parent record, one refers to it as one of the following: | ||
+ for singular, when not populated: a "foreign key" | ||
+ for singular, when populated: a "child record" (aka "foreign record") | ||
+ for plural, when populated: a "collection" (aka "foreign collection") | ||
### Stage 3 query | ||
@@ -262,3 +281,65 @@ | ||
joins: [ | ||
// TODO: document `joins` (@particlebanana/@sgress454 halp!) | ||
// The `joins` array can have 1 or 2 dictionaries inside of it for __each__ populated | ||
// attribute in the query. If the query requires the use of a join table then | ||
// the array will have two items for that population. | ||
{ | ||
// The identity of the parent model | ||
parentCollectionIdentity: 'users', | ||
// The model tableName of the parent (unless specified all keys are using tableNames) | ||
parent: 'user_table_name', | ||
// An alias to use for the join | ||
parentAlias: 'user_table_name__pets', | ||
// For singular associations, the populated attribute will have a schema (since it represents | ||
// a real column). For plural associations, we'll use the primary key column of the parent table. | ||
parentKey: 'id', | ||
// The identity of the child model (in this case the join table) | ||
childCollectionIdentity: 'pets_owners__users_pets', | ||
// The tableName of the child model | ||
child: 'pets_owners__users_pets', | ||
// An alias to use for the join. It's made up of the parent reference + '__' + the attribute to populate | ||
childAlias: 'pets_owners__users_pets__pets', | ||
// The key on the child model that represents the foreign key value | ||
childKey: 'user_pets', | ||
// The original model alias used | ||
alias: 'pets', | ||
// Determines if the parent key is needed on the record. Will be true for | ||
// singular associations otherwise false. | ||
removeParentKey: false, | ||
// Similar to removeParentKey | ||
model: false, | ||
// Flag determining if multiple records will be returned | ||
collection: true | ||
}, | ||
// In this case the "pets" population requires the use of a join table so | ||
// two joins are needed to get the correct data. This dictionary represents | ||
// the connection between the join table and the child table. | ||
{ | ||
// Parent in this case will be the join table | ||
parentCollectionIdentity: 'pets_owners__users_pets', | ||
parent: 'pets_owners__users_pets', | ||
parentAlias: 'pets_owners__users_pets__pets', | ||
parentKey: 'pet_owners', | ||
// Child will be the table that holds the actual record being populated | ||
childCollectionIdentity: 'pets', | ||
child: 'pets', | ||
childAlias: 'pets__pets', | ||
childKey: 'id', | ||
alias: 'pets', | ||
// Flag to show that a join table was used so when joining the records | ||
// take that into account. | ||
junctionTable: true, | ||
removeParentKey: false, | ||
model: false, | ||
collection: true, | ||
// Criteria to use for the child table. | ||
criteria: { | ||
where: {}, | ||
limit: 9007199254740991, | ||
skip: 0, | ||
sort: [{ | ||
id: 'ASC' | ||
}], | ||
select: ['createdAt', 'updatedAt', 'id', 'name'] | ||
} | ||
} | ||
] | ||
@@ -265,0 +346,0 @@ }, |
@@ -16,12 +16,13 @@ // ██╗ ██╗ █████╗ ████████╗███████╗██████╗ ██╗ ██╗███╗ ██╗███████╗ | ||
var BaseMetaModel = require('./waterline/collection'); | ||
var getModel = require('./waterline/utils/ontology/get-model'); | ||
/** | ||
* ORM | ||
* ORM (Waterline) | ||
* | ||
* Construct an ORM instance. | ||
* Construct a Waterline ORM instance. | ||
* | ||
* @constructs {ORM} | ||
* @constructs {Waterline} | ||
*/ | ||
module.exports = function ORM() { | ||
function Waterline() { | ||
@@ -68,3 +69,3 @@ // Start by setting up an array of model definitions. | ||
// Alias for backwards compatibility: | ||
orm.loadCollection = function _loadCollection_is_deprecated(){ | ||
orm.loadCollection = function heyThatsDeprecated(){ | ||
console.warn('\n'+ | ||
@@ -287,3 +288,2 @@ 'Warning: As of Waterline 0.13, `loadCollection()` is now `registerModel()`. Please call that instead.\n'+ | ||
// ╦═╗╔═╗╔╦╗╦ ╦╦═╗╔╗╔ ┌┐┌┌─┐┬ ┬ ┌─┐┬─┐┌┬┐ ┬┌┐┌┌─┐┌┬┐┌─┐┌┐┌┌─┐┌─┐ | ||
@@ -294,4 +294,6 @@ // ╠╦╝║╣ ║ ║ ║╠╦╝║║║ │││├┤ │││ │ │├┬┘│││ ││││└─┐ │ ├─┤││││ ├┤ | ||
}; | ||
} | ||
// Export the Waterline ORM constructor. | ||
module.exports = Waterline; | ||
@@ -303,2 +305,3 @@ | ||
// ╔═╗═╗ ╦╔╦╗╔═╗╔╗╔╔═╗╦╔═╗╔╗╔╔═╗ | ||
@@ -309,3 +312,3 @@ // ║╣ ╔╩╦╝ ║ ║╣ ║║║╚═╗║║ ║║║║╚═╗ | ||
// Expose the generic, stateless BaseMetaModel constructor for direct access from | ||
// vanilla Waterline applications (available as `ORM.Model`) | ||
// vanilla Waterline applications (available as `Waterline.Model`) | ||
// | ||
@@ -321,1 +324,180 @@ // > Note that this is technically a "MetaModel", because it will be "newed up" | ||
// ^^FUTURE: remove this alias | ||
/** | ||
* Waterline.start() | ||
* | ||
* Build and initialize a new Waterline ORM instance using the specified | ||
* userland ontology, including model definitions, datastore configurations, | ||
* and adapters. | ||
* | ||
* --EXPERIMENTAL-- | ||
* | ||
* @param {Dictionary} options | ||
* @property {Dictionary} models | ||
* @property {Dictionary} datastores | ||
* @property {Dictionary} adapters | ||
* @property {Dictionary?} defaultModelSettings | ||
* | ||
* @param {Function} done | ||
* @param {Error?} err | ||
* @param {Ref} orm | ||
*/ | ||
module.exports.start = function (options, done){ | ||
// Verify usage & apply defaults: | ||
if (!_.isFunction(done)) { | ||
throw new Error('Please provide a valid callback function as the 2nd argument to `Waterline.start()`. (Instead, got: `'+done+'`)'); | ||
} | ||
try { | ||
if (!_.isObject(options) || _.isArray(options) || _.isFunction(options)) { | ||
throw new Error('Please provide a valid dictionary (plain JS object) as the 1st argument to `Waterline.start()`. (Instead, got: `'+options+'`)'); | ||
} | ||
if (!_.isObject(options.adapters) || _.isArray(options.adapters) || _.isFunction(options.adapters)) { | ||
throw new Error('`adapters` must be provided as a valid dictionary (plain JS object) of adapter definitions, keyed by adapter identity. (Instead, got: `'+options.adapters+'`)'); | ||
} | ||
if (!_.isObject(options.datastores) || _.isArray(options.datastores) || _.isFunction(options.datastores)) { | ||
throw new Error('`datastores` must be provided as a valid dictionary (plain JS object) of datastore configurations, keyed by datastore name. (Instead, got: `'+options.datastores+'`)'); | ||
} | ||
if (!_.isObject(options.models) || _.isArray(options.models) || _.isFunction(options.models)) { | ||
throw new Error('`models` must be provided as a valid dictionary (plain JS object) of model definitions, keyed by model identity. (Instead, got: `'+options.models+'`)'); | ||
} | ||
if (_.isUndefined(options.defaultModelSettings)) { | ||
options.defaultModelSettings = {}; | ||
} else if (!_.isObject(options.defaultModelSettings) || _.isArray(options.defaultModelSettings) || _.isFunction(options.defaultModelSettings)) { | ||
throw new Error('If specified, `defaultModelSettings` must be a dictionary (plain JavaScript object). (Instead, got: `'+options.defaultModelSettings+'`)'); | ||
} | ||
// Check adapter identities. | ||
_.each(options.adapters, function (adapter, key){ | ||
if (_.isUndefined(adapter.identity)) { | ||
throw new Error('All adapters should declare an `identity`. But the adapter passed in under `'+key+'` has no identity!'); | ||
} | ||
else if (adapter.identity !== key) { | ||
throw new Error('The `identity` explicitly defined on an adapter should exactly match the key under which it is passed in to `Waterline.start()`. But the adapter passed in for key `'+key+'` has an identity that does not match: `'+adapter.identity+'`'); | ||
} | ||
});//</_.each> | ||
// Now go ahead: start building & initializing the ORM. | ||
var orm = new Waterline(); | ||
// Register models (checking model identities along the way). | ||
_.each(options.models, function (userlandModelDef, key){ | ||
if (_.isUndefined(userlandModelDef.identity)) { | ||
userlandModelDef.identity = key; | ||
} | ||
else if (userlandModelDef.identity !== key) { | ||
throw new Error('If `identity` is explicitly defined on a model definition, it should exactly match the key under which it is passed in to `Waterline.start()`. But the model definition passed in for key `'+key+'` has an identity that does not match: `'+userlandModelDef.identity+'`'); | ||
} | ||
orm.registerModel(Waterline.Model.extend(userlandModelDef)); | ||
});//</_.each> | ||
orm.initialize({ | ||
adapters: options.adapters, | ||
datastores: options.datastores, | ||
defaults: options.defaultModelSettings | ||
}, function (err, _classicOntology) { | ||
if (err) { return done(err); } | ||
// Attach two private properties for compatibility's sake. | ||
// (These are necessary for utilities that accept `orm` to work.) | ||
// > But note that we do this as non-enumerable properties | ||
// > to make it less tempting to rely on them in userland code. | ||
Object.defineProperty(orm, 'collections', { | ||
value: _classicOntology.collections | ||
}); | ||
Object.defineProperty(orm, 'datastores', { | ||
value: _classicOntology.datastores | ||
}); | ||
return done(undefined, orm); | ||
}); | ||
} catch (e) { return done(e); } | ||
};//</Waterline.start()> | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// To test quickly: | ||
// ``` | ||
// require('./').start({adapters: { 'sails-foo': { identity: 'sails-foo' } }, datastores: { default: { adapter: 'sails-foo' } }, models: { user: { attributes: {id: {type: 'number'}}, primaryKey: 'id', datastore: 'default'} }}, function(err, _orm){ if(err){throw err;} console.log(_orm); /* and expose as `orm`: */ orm = _orm; }); | ||
// ``` | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
/** | ||
* Waterline.stop() | ||
* | ||
* Tear down the specified Waterline ORM instance. | ||
* | ||
* --EXPERIMENTAL-- | ||
* | ||
* @param {Ref} orm | ||
* | ||
* @param {Function} done | ||
* @param {Error?} err | ||
*/ | ||
module.exports.stop = function (orm, done){ | ||
// Verify usage & apply defaults: | ||
if (!_.isFunction(done)) { | ||
throw new Error('Please provide a valid callback function as the 2nd argument to `Waterline.stop()`. (Instead, got: `'+done+'`)'); | ||
} | ||
try { | ||
if (!_.isObject(orm)) { | ||
throw new Error('Please provide a Waterline ORM instance (obtained from `Waterline.start()`) as the first argument to `Waterline.stop()`. (Instead, got: `'+orm+'`)'); | ||
} | ||
orm.teardown(function (err){ | ||
if (err) { return done(err); } | ||
return done(); | ||
});//_∏_ | ||
} catch (e) { return done(e); } | ||
}; | ||
/** | ||
* Waterline.getModel() | ||
* | ||
* Look up one of an ORM's models by identity. | ||
* (If no matching model is found, this throws an error.) | ||
* | ||
* --EXPERIMENTAL-- | ||
* | ||
* ------------------------------------------------------------------------------------------ | ||
* @param {String} modelIdentity | ||
* The identity of the model this is referring to (e.g. "pet" or "user") | ||
* | ||
* @param {Ref} orm | ||
* The ORM instance to look for the model in. | ||
* ------------------------------------------------------------------------------------------ | ||
* @returns {Ref} [the Waterline model] | ||
* ------------------------------------------------------------------------------------------ | ||
* @throws {Error} If no such model exists. | ||
* E_MODEL_NOT_REGISTERED | ||
* | ||
* @throws {Error} If anything else goes wrong. | ||
* ------------------------------------------------------------------------------------------ | ||
*/ | ||
module.exports.getModel = function (modelIdentity, orm){ | ||
return getModel(modelIdentity, orm); | ||
}; |
@@ -89,5 +89,2 @@ /** | ||
// Set the `adapter` property to an empty dictionary if it is not already truthy. | ||
this.adapter = this.adapter || {}; | ||
// ^^TODO: can we remove this now? | ||
@@ -211,3 +208,4 @@ // Build a dictionary of all lifecycle callbacks applicable to this model, and | ||
* @param {Dictionary?} staticProps | ||
* Optional extra set of properties to attach directly to the new ctor. | ||
* NO LONGER SUPPORTED: An optional, extra set of properties to attach | ||
* directly to the new ctor. | ||
* | ||
@@ -223,5 +221,21 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Sanity checks: | ||
// If a prototypal properties were provided, and one of them is under the `constructor` key, | ||
// then freak out. This is no longer supported, and shouldn't still be in use anywhere. | ||
if (protoProps && _.has(protoProps, 'constructor')) { | ||
throw new Error('Consistency violation: The first argument (`protoProps`) provided to Waterline.Model.extend() should never have a `constructor` property. (This kind of usage is no longer supported.)'); | ||
} | ||
// If any additional custom static properties were specified, then freak out. | ||
// This is no longer supported, and shouldn't still be in use anywhere. | ||
if (!_.isUndefined(staticProps)) { | ||
throw new Error('Consistency violation: Unrecognized extra argument provided to Waterline.Model.extend() (`staticProps` is no longer supported.)'); | ||
} | ||
//--• | ||
// Now proceed with the classical, Backbone-flavor extending. | ||
var newConstructor; | ||
if (protoProps && _.has(protoProps, 'constructor')) { | ||
// TODO: remove support for this if possible-- we don't seem to be relying on it | ||
newConstructor = protoProps.constructor; | ||
@@ -235,4 +249,2 @@ } else { | ||
_.extend(newConstructor, thisConstructor, staticProps); | ||
// ^^TODO: remove support for attaching additional custom static properties if possible | ||
// (doesn't appear to be in use anywhere, and _shouldn't_ be in use anywhere) | ||
@@ -248,3 +260,3 @@ // Create an ad hoc "Surrogate" -- a short-lived, bionic kind of a constructor | ||
// > Why? Well for one thing, this is important so that our new constructor appears | ||
// > to "inherit" from our original constructor. But more a likely more prescient motive | ||
// > to "inherit" from our original constructor. But likely a more prescient motive | ||
// > is so that our new ctor is a proper clone. That is, it's no longer entangled with | ||
@@ -251,0 +263,0 @@ // > the original constructor. |
@@ -7,8 +7,18 @@ /** | ||
var flaverr = require('flaverr'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var parley = require('parley'); | ||
var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
var helpAddToCollection = require('../utils/collection-operations/help-add-to-collection'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('addToCollection'); | ||
/** | ||
* addToCollection() | ||
@@ -36,3 +46,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -44,3 +54,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -73,4 +83,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function addToCollection(/* targetRecordIds, collectionAttrName, associatedIds?, done?, meta? */) { | ||
module.exports = function addToCollection(/* targetRecordIds, collectionAttrName, associatedIds?, explicitCbMaybe?, meta? */) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -87,2 +100,5 @@ var WLModel = this; | ||
// from various utilities calling each other within Waterline itself. | ||
// | ||
// > Note that it'd need to be passed in to the other model methods that | ||
// > get called internally. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -104,9 +120,9 @@ | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (locate the `explicitCbMaybe` callback, and extend the `query` dictionary) | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback) | ||
// (locate the `explicitCbMaybe` callback) | ||
// | ||
@@ -132,12 +148,12 @@ // > Note that we define `args` so that we can insulate access | ||
// | ||
// • addToCollection(____, ____, associatedIds, done, _meta) | ||
// • addToCollection(____, ____, associatedIds, explicitCbMaybe, _meta) | ||
var is3rdArgArray = !_.isUndefined(args[2]); | ||
if (is3rdArgArray) { | ||
query.associatedIds = args[2]; | ||
done = args[3]; | ||
explicitCbMaybe = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • addToCollection(____, ____, done, _meta) | ||
// • addToCollection(____, ____, explicitCbMaybe, _meta) | ||
else { | ||
done = args[2]; | ||
explicitCbMaybe = args[2]; | ||
_meta = args[3]; | ||
@@ -174,100 +190,114 @@ } | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, addToCollection, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.addToCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.addToCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.addToCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.addToCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.addToCollection()` should be '+ | ||
'the ID (or IDs) of associated records to add.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.addToCollection()` should be '+ | ||
'the ID (or IDs) of associated records to add.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids or empty array of associated ids (members) | ||
case 'E_NOOP': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpAddToCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids or empty array of associated ids (members) | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
});//</helpAddToCollection> | ||
}, | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
explicitCbMaybe, | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpAddToCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
_.extend(DEFERRED_METHODS, { | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
});//</helpAddToCollection> | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
}) | ||
);//</parley> | ||
}; |
@@ -7,10 +7,20 @@ /** | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
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 Deferred = require('../utils/query/deferred'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('avg'); | ||
/** | ||
* avg() | ||
@@ -39,7 +49,3 @@ * | ||
* | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -51,4 +57,8 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -72,4 +82,7 @@ * | ||
module.exports = function avg( /* numericAttrName?, criteria?, moreQueryKeys?, done?, meta? */ ) { | ||
module.exports = function avg( /* numericAttrName?, criteria?, explicitCbMaybe?, meta?, moreQueryKeys? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -99,80 +112,35 @@ var WLModel = this; | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (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(){ | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// Additional query keys. | ||
var _moreQueryKeys; | ||
// The metadata container, if one was provided. | ||
var _meta; | ||
// Handle first argument: | ||
// | ||
// • avg(numericAttrName, ...) | ||
// • avg(numericAttrName, explicitCbMaybe, ..., ...) | ||
if (args.length >= 2 && _.isFunction(args[1])) { | ||
query.numericAttrName = args[0]; | ||
explicitCbMaybe = args[1]; | ||
query.meta = args[2]; | ||
if (args[3]) { _.extend(query, args[3]); } | ||
} | ||
// • avg(numericAttrName, criteria, ..., ..., ...) | ||
else { | ||
query.numericAttrName = args[0]; | ||
query.criteria = args[1]; | ||
explicitCbMaybe = args[2]; | ||
query.meta = args[3]; | ||
if (args[4]) { _.extend(query, args[4]); } | ||
} | ||
// Handle double meaning of second argument: | ||
// | ||
// • avg(..., criteria, done, _meta) | ||
var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); | ||
if (is2ndArgDictionary) { | ||
query.criteria = args[1]; | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// • avg(..., done, _meta) | ||
else { | ||
done = args[1]; | ||
_meta = args[2]; | ||
} | ||
// Handle double meaning of third argument: | ||
// | ||
// • avg(..., ..., _moreQueryKeys, done, _meta) | ||
var is3rdArgDictionary = (_.isObject(args[2]) && !_.isFunction(args[2]) && !_.isArray(args[2])); | ||
if (is3rdArgDictionary) { | ||
_moreQueryKeys = args[2]; | ||
done = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • avg(..., ..., done, _meta) | ||
else { | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// Fold in `_moreQueryKeys`, if relevant. | ||
// | ||
// > Userland is prevented from overriding any of the universal keys this way. | ||
if (_moreQueryKeys) { | ||
delete _moreQueryKeys.method; | ||
delete _moreQueryKeys.using; | ||
delete _moreQueryKeys.meta; | ||
_.extend(query, _moreQueryKeys); | ||
} // >- | ||
// Fold in `_meta`, if relevant. | ||
if (!_.isUndefined(_meta)) { | ||
query.meta = _meta; | ||
} // >- | ||
})(); | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -198,99 +166,115 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, avg, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_NUMERIC_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The numeric attr name (i.e. first argument) to `.avg()` should '+ | ||
'be the name of an attribute in this model which is defined with `type: \'number\'`.\n'+ | ||
case 'E_INVALID_NUMERIC_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The numeric attr name (i.e. first argument) to `.avg()` should '+ | ||
'be the name of an attribute in this model which is defined with `type: \'number\'`.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
// ^ custom override for the standard usage error. Note that we use `.details` to get at | ||
// the underlying, lower-level error message (instead of logging redundant stuff from | ||
// the envelope provided by the default error msg.) | ||
// If the criteria wouldn't match anything, that'd basically be like dividing by zero, which is impossible. | ||
case 'E_NOOP': | ||
return done(flaverr({ name: 'UsageError' }, new Error( | ||
'Attempting to compute this average would be like dividing by zero, which is impossible.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
// ^ custom override for the standard usage error. Note that we use `.details` to get at | ||
// the underlying, lower-level error message (instead of logging redundant stuff from | ||
// the envelope provided by the default error msg.) | ||
))); | ||
// If the criteria wouldn't match anything, that'd basically be like dividing by zero, which is impossible. | ||
case 'E_NOOP': | ||
return done(flaverr({ name: 'UsageError' }, new Error( | ||
'Attempting to compute this average would be like dividing by zero, which is impossible.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
))); | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.avg) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.avg) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
adapter.avg(WLModel.datastore, query, function _afterTalkingToAdapter(err, arithmeticMean) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'avg', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
adapter.avg(WLModel.datastore, query, function _afterTalkingToAdapter(err, arithmeticMean) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'avg', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
return done(undefined, arithmeticMean); | ||
return done(undefined, arithmeticMean); | ||
});//</adapter.avg()> | ||
});//</adapter.avg()> | ||
}, | ||
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> | ||
}; |
@@ -6,2 +6,3 @@ /** | ||
var _ = require('@sailshq/lodash'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
@@ -11,6 +12,14 @@ var forgeAdapterError = require('../utils/query/forge-adapter-error'); | ||
var forgeStageThreeQuery = require('../utils/query/forge-stage-three-query'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('count'); | ||
/** | ||
* count() | ||
@@ -36,7 +45,3 @@ * | ||
* | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -48,4 +53,8 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -65,4 +74,7 @@ * | ||
module.exports = function count( /* criteria?, moreQueryKeys?, done?, meta? */ ) { | ||
module.exports = function count( /* criteria?, explicitCbMaybe?, meta?, moreQueryKeys? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -91,64 +103,33 @@ var WLModel = this; | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (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(){ | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// Additional query keys. | ||
var _moreQueryKeys; | ||
// The metadata container, if one was provided. | ||
var _meta; | ||
// Handle first argument: | ||
// | ||
// • count(criteria, ...) | ||
// • count(explicitCbMaybe, ..., ...) | ||
if (args.length >= 1 && _.isFunction(args[0])) { | ||
explicitCbMaybe = args[0]; | ||
query.meta = args[1]; | ||
if (args[2]) { _.extend(query, args[2]); } | ||
} | ||
// • count(criteria, ..., ..., ...) | ||
else { | ||
query.criteria = args[0]; | ||
explicitCbMaybe = args[1]; | ||
query.meta = args[2]; | ||
if (args[3]) { _.extend(query, args[3]); } | ||
} | ||
// Handle double meaning of second argument: | ||
// | ||
// • count(..., moreQueryKeys, done, _meta) | ||
var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); | ||
if (is2ndArgDictionary) { | ||
_moreQueryKeys = args[1]; | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// • count(..., done, _meta) | ||
else { | ||
done = args[1]; | ||
_meta = args[2]; | ||
} | ||
// Fold in `_moreQueryKeys`, if relevant. | ||
// | ||
// > Userland is prevented from overriding any of the universal keys this way. | ||
if (_moreQueryKeys) { | ||
delete _moreQueryKeys.method; | ||
delete _moreQueryKeys.using; | ||
delete _moreQueryKeys.meta; | ||
_.extend(query, _moreQueryKeys); | ||
} // >- | ||
// Fold in `_meta`, if relevant. | ||
if (_meta) { | ||
query.meta = _meta; | ||
} // >- | ||
})(); | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -174,78 +155,94 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, count, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_NOOP': | ||
return done(undefined, 0); | ||
case 'E_NOOP': | ||
return done(undefined, 0); | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.count) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.count) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
adapter.count(WLModel.datastore, query, function _afterTalkingToAdapter(err, numRecords) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'count', modelIdentity, orm); | ||
return done(err); | ||
} | ||
adapter.count(WLModel.datastore, query, function _afterTalkingToAdapter(err, numRecords) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'count', modelIdentity, orm); | ||
return done(err); | ||
} | ||
return done(undefined, numRecords); | ||
return done(undefined, numRecords); | ||
});//</adapter.count()> | ||
});//</adapter.count()> | ||
}, | ||
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> | ||
}; |
/** | ||
* Module Dependencies | ||
* Module dependencies | ||
*/ | ||
@@ -8,2 +8,3 @@ | ||
var async = require('async'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
@@ -13,7 +14,17 @@ var forgeAdapterError = require('../utils/query/forge-adapter-error'); | ||
var forgeStageThreeQuery = require('../utils/query/forge-stage-three-query'); | ||
var Deferred = require('../utils/query/deferred'); | ||
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('createEach'); | ||
/** | ||
* createEach() | ||
@@ -30,3 +41,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -38,3 +49,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -44,4 +55,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function createEach( /* newRecords?, done?, meta? */ ) { | ||
module.exports = function createEach( /* newRecords?, explicitCbMaybe?, meta? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -70,7 +84,7 @@ var WLModel = this; | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback) | ||
// (locate the `explicitCbMaybe` callback) | ||
// | ||
@@ -90,5 +104,5 @@ // > Note that we define `args` so that we can insulate access | ||
// • createEach(..., done, _meta) | ||
// • createEach(..., explicitCbMaybe, _meta) | ||
query.newRecords = args[0]; | ||
done = args[1]; | ||
explicitCbMaybe = args[1]; | ||
_meta = args[2]; | ||
@@ -104,2 +118,3 @@ | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -125,246 +140,275 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// If a callback function was not specified, then build a new `Deferred` and bail now. | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, createEach, query); | ||
} // --• | ||
return parley( | ||
function (done){ | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_NEW_RECORDS': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_INVALID_NEW_RECORDS': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
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); | ||
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(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// - - - - - | ||
// FUTURE: beforeCreateEach lifecycle callback? | ||
// console.log('Successfully forged s2q ::', require('util').inspect(query, {depth:null})); | ||
// - - - - - | ||
// FUTURE: beforeCreateEach lifecycle callback? | ||
// ╔═╗╦ ╦╔═╗╔═╗╦╔═ ┌─┐┌─┐┬─┐ ┌─┐┌┐┌┬ ┬ | ||
// ║ ╠═╣║╣ ║ ╠╩╗ ├┤ │ │├┬┘ ├─┤│││└┬┘ | ||
// ╚═╝╩ ╩╚═╝╚═╝╩ ╩ └ └─┘┴└─ ┴ ┴┘└┘ ┴ | ||
// ┌─┐┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌ ┬─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ | ||
// │ │ ││ │ ├┤ │ │ ││ ││││ ├┬┘├┤ └─┐├┤ │ └─┐ | ||
// └─┘└─┘┴─┘┴─┘└─┘└─┘ ┴ ┴└─┘┘└┘ ┴└─└─┘└─┘└─┘ ┴ └─┘ | ||
// Also removes them from the newRecords before sending to the adapter. | ||
var allCollectionResets = []; | ||
_.each(query.newRecords, function _eachRecord(record) { | ||
// Hold the individual resets | ||
var reset = {}; | ||
// ╔═╗╦ ╦╔═╗╔═╗╦╔═ ┌─┐┌─┐┬─┐ ┌─┐┌┐┌┬ ┬ | ||
// ║ ╠═╣║╣ ║ ╠╩╗ ├┤ │ │├┬┘ ├─┤│││└┬┘ | ||
// ╚═╝╩ ╩╚═╝╚═╝╩ ╩ └ └─┘┴└─ ┴ ┴┘└┘ ┴ | ||
// ┌─┐┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌ ┬─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ | ||
// │ │ ││ │ ├┤ │ │ ││ ││││ ├┬┘├┤ └─┐├┤ │ └─┐ | ||
// └─┘└─┘┴─┘┴─┘└─┘└─┘ ┴ ┴└─┘┘└┘ ┴└─└─┘└─┘└─┘ ┴ └─┘ | ||
// Also removes them from the newRecords before sending to the adapter. | ||
var allCollectionResets = []; | ||
_.each(WLModel.attributes, function _eachKnownAttrDef(attrDef, attrName) { | ||
_.each(query.newRecords, function _eachRecord(record) { | ||
// Hold the individual resets | ||
var reset = {}; | ||
if (attrDef.collection) { | ||
// Only create a reset if the value isn't an empty array. If the value | ||
// is an empty array there isn't any resetting to do. | ||
if (record[attrName].length) { | ||
reset[attrName] = record[attrName]; | ||
} | ||
_.each(WLModel.attributes, function _eachKnownAttrDef(attrDef, attrName) { | ||
// Remove the collection value from the newRecord because the adapter | ||
// doesn't need to do anything during the initial create. | ||
delete record[attrName]; | ||
if (attrDef.collection) { | ||
// Only create a reset if the value isn't an empty array. If the value | ||
// is an empty array there isn't any resetting to do. | ||
if (record[attrName].length) { | ||
reset[attrName] = record[attrName]; | ||
} | ||
// Remove the collection value from the newRecord because the adapter | ||
// doesn't need to do anything during the initial create. | ||
delete record[attrName]; | ||
} | ||
});//</ each known attr def > | ||
allCollectionResets.push(reset); | ||
});//</ each record > | ||
// Hold a variable for the queries `meta` property that could possibly be | ||
// changed by us later on. | ||
var modifiedMeta; | ||
// If any collection resets were specified, force `fetch: true` (meta key) | ||
// so that the adapter will send back the records and we can use them below | ||
// in order to call `resetCollection()`. | ||
var anyActualCollectionResets = _.any(allCollectionResets, function (reset){ | ||
return _.keys(reset).length > 0; | ||
}); | ||
if (anyActualCollectionResets) { | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
// that also has `fetch: true`. | ||
modifiedMeta = _.extend({}, query.meta || {}, { fetch: true }); | ||
}//>- | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.createEach) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
});//</ each known attr def > | ||
allCollectionResets.push(reset); | ||
});//</ each record > | ||
// Allow the query to possibly use the modified meta | ||
query.meta = modifiedMeta || query.meta; | ||
// console.log('Successfully forged S3Q ::', require('util').inspect(query, {depth:null})); | ||
adapter.createEach(WLModel.datastore, query, function(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'createEach', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
// If any collection resets were specified, force `fetch: true` (meta key) | ||
// so that the adapter will send back the records and we can use them below | ||
// in order to call `resetCollection()`. | ||
var anyActualCollectionResets = _.any(allCollectionResets, function (reset){ | ||
return _.keys(reset).length > 0; | ||
}); | ||
if (anyActualCollectionResets) { | ||
query.meta = query.meta || {}; | ||
query.meta.fetch = true; | ||
}//>- | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
var fetch = modifiedMeta || (_.has(query.meta, 'fetch') && query.meta.fetch); | ||
if (!fetch) { | ||
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 `createEach` method. But it did -- which is why this warning is being displayed:\n'+ | ||
'to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
return done(); | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.createEach) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
}//-• | ||
adapter.createEach(WLModel.datastore, query, function(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'createEach', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
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 `createEach` method. But it did -- which is why this warning is being displayed:\n'+ | ||
'to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬─┐┌─┐┌─┐┬ ┬┬ ┌┬┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ ├┬┘├┤ └─┐│ ││ │ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ ┴└─└─┘└─┘└─┘┴─┘┴ | ||
// Attempt to convert the records' column names to attribute names. | ||
var transformationErrors = []; | ||
var transformedRecords = []; | ||
_.each(rawAdapterResult, function(record) { | ||
var transformedRecord; | ||
try { | ||
transformedRecord = WLModel._transformer.unserialize(record); | ||
} catch (e) { | ||
transformationErrors.push(e); | ||
} | ||
return done(); | ||
transformedRecords.push(transformedRecord); | ||
}); | ||
}//-• | ||
if (transformationErrors.length > 0) { | ||
return done(new Error( | ||
'Encountered '+transformationErrors.length+' error(s) processing the record(s) sent back '+ | ||
'from the adapter-- specifically, when converting column names back to attribute names. '+ | ||
'Details: '+ | ||
util.inspect(transformationErrors,{depth:5})+'' | ||
)); | ||
}//-• | ||
// 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(transformedRecords, query.meta, WLModel.identity, orm); | ||
} catch (e) { return done(e); } | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬─┐┌─┐┌─┐┬ ┬┬ ┌┬┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ ├┬┘├┤ └─┐│ ││ │ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ ┴└─└─┘└─┘└─┘┴─┘┴ | ||
// Attempt to convert the records' column names to attribute names. | ||
var transformationErrors = []; | ||
var transformedRecords = []; | ||
_.each(rawAdapterResult, function(record) { | ||
var transformedRecord; | ||
try { | ||
transformedRecord = WLModel._transformer.unserialize(record); | ||
} catch (e) { | ||
transformationErrors.push(e); | ||
} | ||
// ┌─┐┌─┐┬ ┬ ╦═╗╔═╗╔═╗╦ ╔═╗╔═╗╔═╗ ╔═╗╔═╗╦ ╦ ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌─┐┬─┐ | ||
// │ ├─┤│ │ ╠╦╝║╣ ╠═╝║ ╠═╣║ ║╣ ║ ║ ║║ ║ ║╣ ║ ║ ║║ ║║║║ ├┤ │ │├┬┘ | ||
// └─┘┴ ┴┴─┘┴─┘ ╩╚═╚═╝╩ ╩═╝╩ ╩╚═╝╚═╝ ╚═╝╚═╝╩═╝╩═╝╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └ └─┘┴└─ | ||
// ┌─┐─┐ ┬┌─┐┬ ┬┌─┐┬┌┬┐┬ ┬ ┬ ┌─┐┌─┐┌─┐┌─┐┬┌─┐┬┌─┐┌┬┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ┌┴┬┘├─┘│ ││ │ │ │ └┬┘───└─┐├─┘├┤ │ │├┤ │├┤ ││ ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ | ||
// └─┘┴ └─┴ ┴─┘┴└─┘┴ ┴ ┴─┘┴ └─┘┴ └─┘└─┘┴└ ┴└─┘─┴┘ ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
var argsForEachReplaceOp = []; | ||
_.each(transformedRecords, function (record, idx) { | ||
transformedRecords.push(transformedRecord); | ||
}); | ||
// Grab the dictionary of collection resets corresponding to this record. | ||
var reset = allCollectionResets[idx]; | ||
if (transformationErrors.length > 0) { | ||
return done(new Error( | ||
'Encountered '+transformationErrors.length+' error(s) processing the record(s) sent back '+ | ||
'from the adapter-- specifically, when converting column names back to attribute names. '+ | ||
'Details: '+ | ||
util.inspect(transformationErrors,{depth:5})+'' | ||
)); | ||
}//-• | ||
// If there are no resets, then there's no need to build up a replaceCollection() query. | ||
if (_.keys(reset).length === 0) { | ||
return; | ||
}//-• | ||
// 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(transformedRecords, query.meta, WLModel.identity, orm); | ||
} catch (e) { return done(e); } | ||
// Otherwise, build an array of arrays, where each sub-array contains | ||
// the first three arguments that need to be passed in to `replaceCollection()`. | ||
var targetIds = [ record[WLModel.primaryKey] ]; | ||
_.each(_.keys(reset), function (collectionAttrName) { | ||
// (targetId(s), collectionAttrName, associatedPrimaryKeys) | ||
argsForEachReplaceOp.push([ | ||
targetIds, | ||
collectionAttrName, | ||
reset[collectionAttrName] | ||
]); | ||
// ┌─┐┌─┐┬ ┬ ╦═╗╔═╗╔═╗╦ ╔═╗╔═╗╔═╗ ╔═╗╔═╗╦ ╦ ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌─┐┬─┐ | ||
// │ ├─┤│ │ ╠╦╝║╣ ╠═╝║ ╠═╣║ ║╣ ║ ║ ║║ ║ ║╣ ║ ║ ║║ ║║║║ ├┤ │ │├┬┘ | ||
// └─┘┴ ┴┴─┘┴─┘ ╩╚═╚═╝╩ ╩═╝╩ ╩╚═╝╚═╝ ╚═╝╚═╝╩═╝╩═╝╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └ └─┘┴└─ | ||
// ┌─┐─┐ ┬┌─┐┬ ┬┌─┐┬┌┬┐┬ ┬ ┬ ┌─┐┌─┐┌─┐┌─┐┬┌─┐┬┌─┐┌┬┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ┌┴┬┘├─┘│ ││ │ │ │ └┬┘───└─┐├─┘├┤ │ │├┤ │├┤ ││ ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ | ||
// └─┘┴ └─┴ ┴─┘┴└─┘┴ ┴ ┴─┘┴ └─┘┴ └─┘└─┘┴└ ┴└─┘─┴┘ ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
var argsForEachReplaceOp = []; | ||
_.each(transformedRecords, function (record, idx) { | ||
});// </ each key in "reset" > | ||
});//</ each record> | ||
// Grab the dictionary of collection resets corresponding to this record. | ||
var reset = allCollectionResets[idx]; | ||
async.each(argsForEachReplaceOp, function _eachReplaceCollectionOp(argsForReplace, next) { | ||
// If there are no resets, then there's no need to build up a replaceCollection() query. | ||
if (_.keys(reset).length === 0) { | ||
return; | ||
}//-• | ||
// Note that, by using the same `meta`, we use same db connection | ||
// (if one was explicitly passed in, anyway) | ||
WLModel.replaceCollection(argsForReplace[0], argsForReplace[1], argsForReplace[2], function(err) { | ||
if (err) { return next(err); } | ||
return next(); | ||
}, query.meta); | ||
// Otherwise, build an array of arrays, where each sub-array contains | ||
// the first three arguments that need to be passed in to `replaceCollection()`. | ||
var targetIds = [ record[WLModel.primaryKey] ]; | ||
_.each(_.keys(reset), function (collectionAttrName) { | ||
},// ~∞%° | ||
function _afterReplacingAllCollections(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// (targetId(s), collectionAttrName, associatedPrimaryKeys) | ||
argsForEachReplaceOp.push([ | ||
targetIds, | ||
collectionAttrName, | ||
reset[collectionAttrName] | ||
]); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: `afterCreateEach` lifecycle callback? | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
});// </ each key in "reset" > | ||
});//</ each record> | ||
return done(undefined, transformedRecords); | ||
async.each(argsForEachReplaceOp, function _eachReplaceCollectionOp(argsForReplace, next) { | ||
});//</async.each()> | ||
});//</adapter.createEach()> | ||
// Note that, by using the same `meta`, we use same db connection | ||
// (if one was explicitly passed in, anyway) | ||
WLModel.replaceCollection(argsForReplace[0], argsForReplace[1], argsForReplace[2], function(err) { | ||
if (err) { return next(err); } | ||
return next(); | ||
}, query.meta); | ||
}, | ||
},// ~∞%° | ||
function _afterReplacingAllCollections(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: `afterCreateEach` lifecycle callback? | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
explicitCbMaybe, | ||
return done(undefined, transformedRecords); | ||
});//</async.each()> | ||
});//</adapter.createEach()> | ||
_.extend(DEFERRED_METHODS, { | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
}) | ||
);//</parley> | ||
}; |
/** | ||
* Module Dependencies | ||
* Module dependencies | ||
*/ | ||
@@ -9,20 +9,72 @@ | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
var forgeAdapterError = require('../utils/query/forge-adapter-error'); | ||
var Deferred = require('../utils/query/deferred'); | ||
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'); | ||
/** | ||
* Create a new record | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('create'); | ||
/** | ||
* create() | ||
* | ||
* @param {Object || Array} values for single model or array of multiple values | ||
* @param {Function} callback | ||
* @return Deferred object if no callback | ||
* Create a new record using the specified initial values. | ||
* | ||
* ``` | ||
* // Create a new bank account with a half million dollars, | ||
* // and associate it with the logged in user. | ||
* BankAccount.create({ | ||
* balance: 500000, | ||
* owner: req.session.userId | ||
* }) | ||
* .exec(function(err) { | ||
* // ... | ||
* }); | ||
* ``` | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* | ||
* Usage without deferred object: | ||
* ================================================ | ||
* | ||
* @param {Dictionary?} newRecord | ||
* | ||
* @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?} newRecord | ||
* | ||
* @qkey {Dictionary?} meta | ||
* @qkey {String} using | ||
* @qkey {String} method | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
*/ | ||
module.exports = function create(values, done, metaContainer) { | ||
module.exports = function create(newRecord, explicitCbMaybe, metaContainer) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -36,6 +88,7 @@ var WLModel = this; | ||
// Build initial query. | ||
var query = { | ||
method: 'create', | ||
using: modelIdentity, | ||
newRecord: values, | ||
newRecord: newRecord, | ||
meta: metaContainer | ||
@@ -75,235 +128,268 @@ }; | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// Return Deferred or pass to adapter | ||
if (typeof done !== 'function') { | ||
return new Deferred(WLModel, WLModel.create, query); | ||
} | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// If a callback function was not specified, then build a new Deferred and bail now. | ||
// | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// 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_NEW_RECORD': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid new record(s).\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
// > This method will be called AGAIN automatically when the Deferred is executed. | ||
// > and next time, it'll have a callback. | ||
return parley( | ||
default: | ||
return done(e); | ||
} | ||
} | ||
function (done){ | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
// ╔╗ ╔═╗╔═╗╔═╗╦═╗╔═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠╩╗║╣ ╠╣ ║ ║╠╦╝║╣ │ ├┬┘├┤ ├─┤ │ ├┤ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╚═╝╚═╝╚ ╚═╝╩╚═╚═╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Determine what to do about running "before" lifecycle callbacks | ||
(function _maybeRunBeforeLC(proceed){ | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// 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_NEW_RECORD': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid new record(s).\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
// 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); | ||
}//-• | ||
default: | ||
return done(e); | ||
} | ||
} | ||
// If there is no relevant "before" lifecycle callback, then just proceed. | ||
if (!_.has(WLModel._callbacks, 'beforeCreate')) { | ||
return proceed(undefined, query); | ||
}//-• | ||
// IWMIH, run the "before" lifecycle callback. | ||
WLModel._callbacks.beforeCreate(query.newRecord, function(err){ | ||
if (err) { return proceed(err); } | ||
return proceed(undefined, query); | ||
}); | ||
// ╔╗ ╔═╗╔═╗╔═╗╦═╗╔═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠╩╗║╣ ╠╣ ║ ║╠╦╝║╣ │ ├┬┘├┤ ├─┤ │ ├┤ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╚═╝╚═╝╚ ╚═╝╩╚═╚═╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Determine what to do about running "before" lifecycle callbacks | ||
(function _maybeRunBeforeLC(proceed){ | ||
})(function _afterPotentiallyRunningBeforeLC(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// 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); | ||
}//-• | ||
// ╔═╗╦ ╦╔═╗╔═╗╦╔═ ┌─┐┌─┐┬─┐ ┌─┐┌┐┌┬ ┬ | ||
// ║ ╠═╣║╣ ║ ╠╩╗ ├┤ │ │├┬┘ ├─┤│││└┬┘ | ||
// ╚═╝╩ ╩╚═╝╚═╝╩ ╩ └ └─┘┴└─ ┴ ┴┘└┘ ┴ | ||
// ┌─┐┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌ ┬─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ | ||
// │ │ ││ │ ├┤ │ │ ││ ││││ ├┬┘├┤ └─┐├┤ │ └─┐ | ||
// └─┘└─┘┴─┘┴─┘└─┘└─┘ ┴ ┴└─┘┘└┘ ┴└─└─┘└─┘└─┘ ┴ └─┘ | ||
// Also removes them from the newRecord before sending to the adapter. | ||
var collectionResets = {}; | ||
_.each(WLModel.attributes, function _eachKnownAttrDef(attrDef, attrName) { | ||
if (attrDef.collection) { | ||
// Only create a reset if the value isn't an empty array. If the value | ||
// is an empty array there isn't any resetting to do. | ||
if (query.newRecord[attrName].length) { | ||
collectionResets[attrName] = query.newRecord[attrName]; | ||
// If there is no relevant "before" lifecycle callback, then just proceed. | ||
if (!_.has(WLModel._callbacks, 'beforeCreate')) { | ||
return proceed(undefined, query); | ||
}//-• | ||
// IWMIH, run the "before" lifecycle callback. | ||
WLModel._callbacks.beforeCreate(query.newRecord, function(err){ | ||
if (err) { return proceed(err); } | ||
return proceed(undefined, query); | ||
}); | ||
})(function _afterPotentiallyRunningBeforeLC(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// Remove the collection value from the newRecord because the adapter | ||
// doesn't need to do anything during the initial create. | ||
delete query.newRecord[attrName]; | ||
} | ||
});//</ each known attribute def > | ||
// ╔═╗╦ ╦╔═╗╔═╗╦╔═ ┌─┐┌─┐┬─┐ ┌─┐┌┐┌┬ ┬ | ||
// ║ ╠═╣║╣ ║ ╠╩╗ ├┤ │ │├┬┘ ├─┤│││└┬┘ | ||
// ╚═╝╩ ╩╚═╝╚═╝╩ ╩ └ └─┘┴└─ ┴ ┴┘└┘ ┴ | ||
// ┌─┐┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌ ┬─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ | ||
// │ │ ││ │ ├┤ │ │ ││ ││││ ├┬┘├┤ └─┐├┤ │ └─┐ | ||
// └─┘└─┘┴─┘┴─┘└─┘└─┘ ┴ ┴└─┘┘└┘ ┴└─└─┘└─┘└─┘ ┴ └─┘ | ||
// Also removes them from the newRecord before sending to the adapter. | ||
var collectionResets = {}; | ||
_.each(WLModel.attributes, function _eachKnownAttrDef(attrDef, attrName) { | ||
if (attrDef.collection) { | ||
// Only create a reset if the value isn't an empty array. If the value | ||
// is an empty array there isn't any resetting to do. | ||
if (query.newRecord[attrName].length) { | ||
collectionResets[attrName] = query.newRecord[attrName]; | ||
} | ||
// If any collection resets were specified, force `fetch: true` (meta key) | ||
// so that we can use it below. | ||
if (_.keys(collectionResets).length > 0) { | ||
query.meta = query.meta || {}; | ||
query.meta.fetch = true; | ||
}//>- | ||
// Remove the collection value from the newRecord because the adapter | ||
// doesn't need to do anything during the initial create. | ||
delete query.newRecord[attrName]; | ||
} | ||
});//</ each known attribute def > | ||
// Hold a variable for the queries `meta` property that could possibly be | ||
// changed by us later on. | ||
var modifiedMeta; | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
// If any collection resets were specified, force `fetch: true` (meta key) | ||
// so that we can use it below. | ||
if (_.keys(collectionResets).length > 0) { | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
// that also has `fetch: true`. | ||
modifiedMeta = _.extend({}, query.meta || {}, { fetch: true }); | ||
}//>- | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.create) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
// And call the adapter method. | ||
adapter.create(WLModel.datastore, query, function _afterTalkingToAdapter(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'create', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.create) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
// Allow the query to possibly use the modified meta | ||
query.meta = modifiedMeta || query.meta; | ||
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 `create` method. But it did -- which is why this warning is being displayed:\n'+ | ||
'to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
// And call the adapter method. | ||
adapter.create(WLModel.datastore, query, function _afterTalkingToAdapter(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'create', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
return done(); | ||
}//-• | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
var fetch = modifiedMeta || (_.has(query.meta, 'fetch') && query.meta.fetch); | ||
if (!fetch) { | ||
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 `create` method. But it did -- which is why this warning is being displayed:\n'+ | ||
'to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
return done(); | ||
// Sanity check: | ||
if (!_.isObject(rawAdapterResult) || _.isArray(rawAdapterResult) || _.isFunction(rawAdapterResult)) { | ||
return done(new Error('Consistency violation: expected `create` adapter method to send back the created record b/c `fetch: true` was enabled. But instead, got: ' + util.inspect(rawAdapterResult, {depth:5})+'')); | ||
} | ||
}//-• | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬─┐┌─┐┌─┐┬ ┬┬ ┌┬┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ ├┬┘├┤ └─┐│ ││ │ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ ┴└─└─┘└─┘└─┘┴─┘┴ | ||
// Attempt to convert the record's column names to attribute names. | ||
var transformedRecord; | ||
try { | ||
transformedRecord = WLModel._transformer.unserialize(rawAdapterResult); | ||
} catch (e) { return done(e); } | ||
// 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([ transformedRecord ], query.meta, modelIdentity, orm); | ||
} catch (e) { return done(e); } | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
// Sanity check: | ||
if (!_.isObject(rawAdapterResult) || _.isArray(rawAdapterResult) || _.isFunction(rawAdapterResult)) { | ||
return done(new Error('Consistency violation: expected `create` adapter method to send back the created record b/c `fetch: true` was enabled. But instead, got: ' + util.inspect(rawAdapterResult, {depth:5})+'')); | ||
} | ||
// ┌─┐┌─┐┬ ┬ ╦═╗╔═╗╔═╗╦ ╔═╗╔═╗╔═╗ ╔═╗╔═╗╦ ╦ ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌─┐┬─┐ | ||
// │ ├─┤│ │ ╠╦╝║╣ ╠═╝║ ╠═╣║ ║╣ ║ ║ ║║ ║ ║╣ ║ ║ ║║ ║║║║ ├┤ │ │├┬┘ | ||
// └─┘┴ ┴┴─┘┴─┘ ╩╚═╚═╝╩ ╩═╝╩ ╩╚═╝╚═╝ ╚═╝╚═╝╩═╝╩═╝╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └ └─┘┴└─ | ||
// ┌─┐─┐ ┬┌─┐┬ ┬┌─┐┬┌┬┐┬ ┬ ┬ ┌─┐┌─┐┌─┐┌─┐┬┌─┐┬┌─┐┌┬┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ┌┴┬┘├─┘│ ││ │ │ │ └┬┘───└─┐├─┘├┤ │ │├┤ │├┤ ││ ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ | ||
// └─┘┴ └─┴ ┴─┘┴└─┘┴ ┴ ┴─┘┴ └─┘┴ └─┘└─┘┴└ ┴└─┘─┴┘ ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
var targetId = transformedRecord[WLModel.primaryKey]; | ||
async.each(_.keys(collectionResets), function _eachReplaceCollectionOp(collectionAttrName, next) { | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬─┐┌─┐┌─┐┬ ┬┬ ┌┬┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ ├┬┘├┤ └─┐│ ││ │ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ ┴└─└─┘└─┘└─┘┴─┘┴ | ||
// Attempt to convert the record's column names to attribute names. | ||
var transformedRecord; | ||
try { | ||
transformedRecord = WLModel._transformer.unserialize(rawAdapterResult); | ||
} catch (e) { return done(e); } | ||
WLModel.replaceCollection(targetId, collectionAttrName, collectionResets[collectionAttrName], function(err){ | ||
if (err) { return next(err); } | ||
return next(); | ||
}, query.meta); | ||
// 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([ transformedRecord ], query.meta, modelIdentity, orm); | ||
} catch (e) { return done(e); } | ||
},// ~∞%° | ||
function _afterReplacingAllCollections(err) { | ||
if (err) { return done(err); } | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ │ ├┬┘├┤ ├─┤ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ └─┘┴└─└─┘┴ ┴ ┴ └─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
(function _maybeRunAfterLC(proceed){ | ||
// ┌─┐┌─┐┬ ┬ ╦═╗╔═╗╔═╗╦ ╔═╗╔═╗╔═╗ ╔═╗╔═╗╦ ╦ ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌─┐┬─┐ | ||
// │ ├─┤│ │ ╠╦╝║╣ ╠═╝║ ╠═╣║ ║╣ ║ ║ ║║ ║ ║╣ ║ ║ ║║ ║║║║ ├┤ │ │├┬┘ | ||
// └─┘┴ ┴┴─┘┴─┘ ╩╚═╚═╝╩ ╩═╝╩ ╩╚═╝╚═╝ ╚═╝╚═╝╩═╝╩═╝╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └ └─┘┴└─ | ||
// ┌─┐─┐ ┬┌─┐┬ ┬┌─┐┬┌┬┐┬ ┬ ┬ ┌─┐┌─┐┌─┐┌─┐┬┌─┐┬┌─┐┌┬┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ | ||
// ├┤ ┌┴┬┘├─┘│ ││ │ │ │ └┬┘───└─┐├─┘├┤ │ │├┤ │├┤ ││ ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ | ||
// └─┘┴ └─┴ ┴─┘┴└─┘┴ ┴ ┴─┘┴ └─┘┴ └─┘└─┘┴└ ┴└─┘─┴┘ ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ | ||
var targetId = transformedRecord[WLModel.primaryKey]; | ||
async.each(_.keys(collectionResets), function _eachReplaceCollectionOp(collectionAttrName, next) { | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run the LC. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return proceed(undefined, transformedRecord); | ||
}//-• | ||
WLModel.replaceCollection(targetId, collectionAttrName, collectionResets[collectionAttrName], function(err){ | ||
if (err) { return next(err); } | ||
return next(); | ||
}, query.meta); | ||
// If no afterCreate callback defined, just proceed. | ||
if (!_.has(WLModel._callbacks, 'afterCreate')) { | ||
return proceed(undefined, transformedRecord); | ||
}//-• | ||
},// ~∞%° | ||
function _afterReplacingAllCollections(err) { | ||
if (err) { return done(err); } | ||
// Otherwise, run it. | ||
return WLModel._callbacks.afterCreate(transformedRecord, function(err) { | ||
if (err) { | ||
return proceed(err); | ||
} | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ │ ├┬┘├┤ ├─┤ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ └─┘┴└─└─┘┴ ┴ ┴ └─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
(function _maybeRunAfterLC(proceed){ | ||
return proceed(undefined, transformedRecord); | ||
}); | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run the LC. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return proceed(undefined, transformedRecord); | ||
}//-• | ||
})(function _afterPotentiallyRunningAfterLC(err, transformedRecord) { | ||
if (err) { return done(err); } | ||
// If no afterCreate callback defined, just proceed. | ||
if (!_.has(WLModel._callbacks, 'afterCreate')) { | ||
return proceed(undefined, transformedRecord); | ||
}//-• | ||
// Return the new record. | ||
return done(undefined, transformedRecord); | ||
// Otherwise, run it. | ||
return WLModel._callbacks.afterCreate(transformedRecord, function(err) { | ||
if (err) { | ||
return proceed(err); | ||
} | ||
});//</ ran "after" lifecycle callback, maybe > | ||
return proceed(undefined, transformedRecord); | ||
}); | ||
});//</ async.each() (calling replaceCollection() for each explicitly-specified plural association) > | ||
});//</ adapter.create() > | ||
});//</ ran "before" lifecycle callback, maybe > | ||
})(function _afterPotentiallyRunningAfterLC(err, transformedRecord) { | ||
if (err) { return done(err); } | ||
// Return the new record. | ||
return done(undefined, transformedRecord); | ||
});//</ ran "after" lifecycle callback, maybe > | ||
});//</ async.each() (calling replaceCollection() for each explicitly-specified plural association) > | ||
});//</ adapter.create() > | ||
});//</ ran "before" lifecycle callback, maybe > | ||
}, | ||
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> | ||
}; |
@@ -5,3 +5,2 @@ /** | ||
var async = require('async'); | ||
var util = require('util'); | ||
@@ -11,2 +10,3 @@ var async = require('async'); | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
@@ -16,16 +16,64 @@ var forgeAdapterError = require('../utils/query/forge-adapter-error'); | ||
var forgeStageThreeQuery = require('../utils/query/forge-stage-three-query'); | ||
var Deferred = require('../utils/query/deferred'); | ||
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'); | ||
/** | ||
* Destroy records matching the criteria. | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('destroy'); | ||
/** | ||
* destroy() | ||
* | ||
* @param {Dictionary} criteria to destroy | ||
* @param {Function} callback | ||
* @return {Deferred} if no callback | ||
* 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, done, metaContainer) { | ||
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. | ||
@@ -39,2 +87,10 @@ var WLModel = this; | ||
// Build initial query. | ||
var query = { | ||
method: 'destroy', | ||
using: modelIdentity, | ||
criteria: undefined, | ||
meta: undefined | ||
}; | ||
// ██╗ ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗ ██████╗███████╗ | ||
@@ -50,8 +106,21 @@ // ██║ ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║██╔════╝██╔════╝ | ||
// The explicit callback, if one was provided. | ||
var explicitCbMaybe; | ||
if (typeof criteria === 'function') { | ||
done = criteria; | ||
criteria = {}; | ||
// 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]; | ||
} | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -77,395 +146,411 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// Return Deferred or pass to adapter | ||
if (typeof done !== 'function') { | ||
return new Deferred(WLModel, WLModel.destroy, { | ||
method: 'destroy', | ||
criteria: criteria | ||
}); | ||
} | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
// 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( | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
// This ensures a normalized format. | ||
var query = { | ||
method: 'destroy', | ||
using: modelIdentity, | ||
criteria: criteria, | ||
meta: metaContainer | ||
}; | ||
function (done){ | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_CRITERIA': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid criteria.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
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 = []; | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// 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' }, | ||
new Error( | ||
'Invalid criteria.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
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`.')); | ||
} | ||
}//>- | ||
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); | ||
} | ||
// ================================================================================ | ||
// 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); | ||
// ================================================================================ | ||
// 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); | ||
}); | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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 _afterRunningBeforeLC(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// ┬ ┌─┐┌─┐┬┌─┬ ┬┌─┐ ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ | ||
// │ │ ││ │├┴┐│ │├─┘ ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘ | ||
// ┴─┘└─┘└─┘┴ ┴└─┘┴ ┴ ┴─┴┘┴ ┴┴ ┴ └─┘┴└─ | ||
// Look up the appropriate adapter to use for this model. | ||
// ┬┌─┐ ╔═╗╔═╗╔═╗╔═╗╔═╗╔╦╗╔═╗ ┌─┐┌┐┌┌─┐┌┐ ┬ ┌─┐┌┬┐ ┌┬┐┬ ┬┌─┐┌┐┌ | ||
// │├┤ ║ ╠═╣╚═╗║ ╠═╣ ║║║╣ ├┤ │││├─┤├┴┐│ ├┤ ││ │ ├─┤├┤ │││ | ||
// ┴└ ╚═╝╩ ╩╚═╝╚═╝╩ ╩═╩╝╚═╝ └─┘┘└┘┴ ┴└─┘┴─┘└─┘─┴┘┘ ┴ ┴ ┴└─┘┘└┘ | ||
// ┌─┐┬┌┐┌┌┬┐ ╦╔╦╗╔═╗ ┌┬┐┌─┐ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ | ||
// ├┤ ││││ ││ ║ ║║╚═╗ │ │ │ ││├┤ └─┐ │ ├┬┘│ │└┬┘ | ||
// └ ┴┘└┘─┴┘ ╩═╩╝╚═╝ ┴ └─┘ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ | ||
(function _maybeFindIdsToDestroy(proceed) { | ||
// 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.')); | ||
} | ||
// If `cascade` meta key is NOT enabled, then just proceed. | ||
if (!query.meta || !query.meta.cascade) { | ||
return proceed(); | ||
} | ||
// 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.')); | ||
} | ||
// 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()) | ||
// If `cascade` is enabled, do an extra assertion... | ||
if (query.meta && query.meta.cascade){ | ||
// 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 | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// 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`.')); | ||
} | ||
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); } | ||
// ================================================================================ | ||
// 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 we'll actually perform the `destroy`. | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// 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); | ||
}//-• | ||
// ┬┌─┐ ╔═╗╔═╗╔═╗╔═╗╔═╗╔╦╗╔═╗ ┌─┐┌┐┌┌─┐┌┐ ┬ ┌─┐┌┬┐ ┌┬┐┬ ┬┌─┐┌┐┌ | ||
// │├┤ ║ ╠═╣╚═╗║ ╠═╣ ║║║╣ ├┤ │││├─┤├┴┐│ ├┤ ││ │ ├─┤├┤ │││ | ||
// ┴└ ╚═╝╩ ╩╚═╝╚═╝╩ ╩═╩╝╚═╝ └─┘┘└┘┴ ┴└─┘┴─┘└─┘─┴┘┘ ┴ ┴ ┴└─┘┘└┘ | ||
// ┌─┐┬┌┐┌┌┬┐ ╦╔╦╗╔═╗ ┌┬┐┌─┐ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ | ||
// ├┤ ││││ ││ ║ ║║╚═╗ │ │ │ ││├┤ └─┐ │ ├┬┘│ │└┬┘ | ||
// └ ┴┘└┘─┴┘ ╩═╩╝╚═╝ ┴ └─┘ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ | ||
(function _maybeFindIdsToDestroy(proceed) { | ||
// ╦═╗╔═╗╦╔╗╔ ╔╦╗╔═╗╦ ╦╔╗╔ ╔╦╗╔═╗╔═╗╔╦╗╦═╗╦ ╦╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌┐┌┌┬┐┌─┐ | ||
// ╠╦╝╠═╣║║║║ ║║║ ║║║║║║║ ║║║╣ ╚═╗ ║ ╠╦╝║ ║║ ║ ║║ ║║║║ │ ││││ │ │ │ | ||
// ╩╚═╩ ╩╩╝╚╝ ═╩╝╚═╝╚╩╝╝╚╝ ═╩╝╚═╝╚═╝ ╩ ╩╚═╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └─┘┘└┘ ┴ └─┘ | ||
// ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ ┌─ ┬ ┌─┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ ─┐ | ||
// ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ │ │ ├┤ │ ├─┤└─┐│ ├─┤ ││├┤ │ | ||
// ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ └─ ┴o└─┘o └─┘┴ ┴└─┘└─┘┴ ┴─┴┘└─┘ ─┘ | ||
(function _maybeWipeAssociatedCollections(proceed) { | ||
// If `cascade` meta key is NOT enabled, then just proceed. | ||
if (!query.meta || !query.meta.cascade) { | ||
return 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()) | ||
// 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})+'')); | ||
} | ||
// 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 | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// --• | ||
// Now we'll clear out collections belonging to the specified records. | ||
// (i.e. use `replaceCollection` to wipe them all out to be `[]`) | ||
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); | ||
// 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(); | ||
}//-• | ||
});//</adapter.find()> | ||
// 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) { | ||
})(function _afterPotentiallyLookingUpRecordsToCascade(err, idsOfRecordsBeingDestroyedMaybe) { | ||
if (err) { return done(err); } | ||
var attrDef = WLModel.attributes[attrName]; | ||
// Skip everything other than collection attributes. | ||
if (!attrDef.collection){ return next(); } | ||
// Now we'll actually perform the `destroy`. | ||
// But otherwise, this is a collection attribute. So wipe it. | ||
WLModel.replaceCollection(idsOfRecordsBeingDestroyedMaybe, attrName, [], function (err) { | ||
if (err) { return next(err); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// 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); | ||
}//-• | ||
return next(); | ||
}, query.meta);//</.replaceCollection()> | ||
// ╦═╗╔═╗╦╔╗╔ ╔╦╗╔═╗╦ ╦╔╗╔ ╔╦╗╔═╗╔═╗╔╦╗╦═╗╦ ╦╔═╗╔╦╗╦╔═╗╔╗╔ ┌─┐┌┐┌┌┬┐┌─┐ | ||
// ╠╦╝╠═╣║║║║ ║║║ ║║║║║║║ ║║║╣ ╚═╗ ║ ╠╦╝║ ║║ ║ ║║ ║║║║ │ ││││ │ │ │ | ||
// ╩╚═╩ ╩╩╝╚╝ ═╩╝╚═╝╚╩╝╝╚╝ ═╩╝╚═╝╚═╝ ╩ ╩╚═╚═╝╚═╝ ╩ ╩╚═╝╝╚╝ └─┘┘└┘ ┴ └─┘ | ||
// ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ ┌─ ┬ ┌─┐ ┌─┐┌─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ ─┐ | ||
// ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││└─┐ │ │ ├┤ │ ├─┤└─┐│ ├─┤ ││├┤ │ | ||
// ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘└─┘ └─ ┴o└─┘o └─┘┴ ┴└─┘└─┘┴ ┴─┴┘└─┘ ─┘ | ||
(function _maybeWipeAssociatedCollections(proceed) { | ||
},// ~∞%° | ||
function _afterwards(err) { | ||
if (err) { return proceed(err); } | ||
// If `cascade` meta key is NOT enabled, then just proceed. | ||
if (!query.meta || !query.meta.cascade) { | ||
return proceed(); | ||
} | ||
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})+'')); | ||
} | ||
});//</ async.each > | ||
// --• | ||
// Now we'll clear out collections belonging to the specified records. | ||
// (i.e. use `replaceCollection` to wipe them all out to be `[]`) | ||
})(function _afterPotentiallyWipingCollections(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┐ ┌┐ ┬ ┬┌┬┐ ┌─┐┌┐┌┬ ┬ ┬ ┬┌─┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├┬┘├┤ │ │ │├┬┘ ││└─┐ ├┴┐│ │ │ │ │││││ └┬┘ │├┤ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴└─└─┘└─┘└─┘┴└──┴┘└─┘ooo└─┘└─┘ ┴ └─┘┘└┘┴─┘┴ ┴└ | ||
// ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
(function _maybeTransformRecords(proceed){ | ||
// 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(); | ||
}//-• | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
// 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) { | ||
if (!_.isUndefined(rawAdapterResult) && _.isArray(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! And since it\'s an array, displaying this\n'+ | ||
'warning to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
var attrDef = WLModel.attributes[attrName]; | ||
// Continue on. | ||
return proceed(); | ||
// 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) { return next(err); } | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
return next(); | ||
// 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})+'' | ||
)); | ||
}//-• | ||
}, query.meta);//</.replaceCollection()> | ||
// 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); } | ||
},// ~∞%° | ||
function _afterwards(err) { | ||
if (err) { return proceed(err); } | ||
// 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); } | ||
return proceed(); | ||
// Now continue on. | ||
return proceed(undefined, transformedRecords); | ||
});//</ async.each > | ||
})(function (err, transformedRecordsMaybe){ | ||
if (err) { | ||
return done(err); | ||
} | ||
})(function _afterPotentiallyWipingCollections(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ ││├┤ └─┐ │ ├┬┘│ │└┬┘ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run "after" lifecycle callback AGAIN and AGAIN- once for each record. | ||
// ============================================================ | ||
async.each(transformedRecordsMaybe, function _eachRecord(record, next) { | ||
// ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┐ ┌┐ ┬ ┬┌┬┐ ┌─┐┌┐┌┬ ┬ ┬ ┬┌─┐ | ||
// ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ ├┬┘├┤ │ │ │├┬┘ ││└─┐ ├┴┐│ │ │ │ │││││ └┬┘ │├┤ | ||
// ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ ┴└─└─┘└─┘└─┘┴└──┴┘└─┘ooo└─┘└─┘ ┴ └─┘┘└┘┴─┘┴ ┴└ | ||
// ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
(function _maybeTransformRecords(proceed){ | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run any of | ||
// the methods. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return next(); | ||
} | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
// Skip "after" lifecycle callback, if not defined. | ||
if (!_.has(WLModel._callbacks, 'afterDestroy')) { | ||
return next(); | ||
} | ||
if (!_.isUndefined(rawAdapterResult) && _.isArray(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! And since it\'s an array, displaying this\n'+ | ||
'warning to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
// Otherwise run it. | ||
WLModel._callbacks.afterDestroy(record, function _afterMaybeRunningAfterDestroyForThisRecord(err) { | ||
if (err) { | ||
return next(err); | ||
} | ||
// Continue on. | ||
return proceed(); | ||
return next(); | ||
}); | ||
}//-• | ||
},// ~∞%° | ||
function _afterIteratingOverRecords(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// IWMIH then we know that `fetch: true` meta key was set, and so the | ||
// adapter should have sent back an array. | ||
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 > | ||
}); // </ afterPotentiallyLookingUpRecordsToCascade > | ||
}); // </ _afterRunningBeforeLC > | ||
}, | ||
// 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); } | ||
explicitCbMaybe, | ||
// 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); | ||
_.extend(DEFERRED_METHODS, { | ||
})(function (err, transformedRecordsMaybe){ | ||
if (err) { return done(err); } | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┌┬┐┌─┐┌─┐┌┬┐┬─┐┌─┐┬ ┬ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ ││├┤ └─┐ │ ├┬┘│ │└┬┘ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ ─┴┘└─┘└─┘ ┴ ┴└─└─┘ ┴ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run "after" lifecycle callback, if appropriate. | ||
// | ||
// Note that we skip it if any of the following are true: | ||
// • `skipAllLifecycleCallbacks` flag is enabled | ||
// • there IS no relevant lifecycle callback | ||
(function _runAfterLC(proceed) { | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
var dontRunAfterLC = ( | ||
(query.meta && query.meta.skipAllLifecycleCallbacks) || | ||
!_.has(WLModel._callbacks, 'afterDestroy') | ||
); | ||
if (dontRunAfterLC) { | ||
return proceed(undefined, transformedRecordsMaybe); | ||
} | ||
}) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: normalize this behavior (currently, it's kind of inconsistent vs update/destroy/create) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
return WLModel._callbacks.afterDestroy(transformedRecordsMaybe, function(err) { | ||
if (err) { return proceed(err); } | ||
return proceed(undefined, transformedRecordsMaybe); | ||
}); | ||
})(function _afterRunningAfterLC(err, transformedRecordsMaybe) { | ||
if (err) { return done(err); } | ||
);//</parley> | ||
return done(undefined, transformedRecordsMaybe); | ||
}); // </ after potentially running after LC > | ||
});//</ after determining (and potentially transforming) the result from the adapter > | ||
}); // </ _afterPotentiallyWipingCollections > | ||
}); // </ adapter.destroy > | ||
}); // </ afterPotentiallyLookingUpRecordsToCascade > | ||
}); // </ _afterRunningBeforeLC > | ||
}; |
@@ -8,10 +8,20 @@ /** | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
var Deferred = require('../utils/query/deferred'); | ||
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() | ||
@@ -39,3 +49,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -47,3 +57,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -65,4 +75,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function findOne( /* criteria?, populates?, done?, meta? */ ) { | ||
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. | ||
@@ -91,7 +104,7 @@ var WLModel = this; | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (locate the `explicitCbMaybe` callback, and extend the `query` dictionary) | ||
// | ||
@@ -114,12 +127,12 @@ // > Note that we define `args` so that we can insulate access | ||
// | ||
// • findOne(..., populates, done, _meta) | ||
// • findOne(..., populates, explicitCbMaybe, _meta) | ||
var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); | ||
if (is2ndArgDictionary) { | ||
query.populates = args[1]; | ||
done = args[2]; | ||
explicitCbMaybe = args[2]; | ||
_meta = args[3]; | ||
} | ||
// • findOne(..., done, _meta) | ||
// • findOne(..., explicitCbMaybe, _meta) | ||
else { | ||
done = args[1]; | ||
explicitCbMaybe = args[1]; | ||
_meta = args[2]; | ||
@@ -156,174 +169,192 @@ } | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, findOne, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// 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' | ||
}, | ||
new Error( | ||
'Invalid criteria.\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_CRITERIA': | ||
return done( | ||
flaverr( | ||
{ | ||
name: 'UsageError' | ||
}, | ||
new Error( | ||
'Invalid criteria.\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_POPULATES': | ||
return done( | ||
flaverr({ | ||
name: 'UsageError' | ||
}, | ||
new Error( | ||
'Invalid populate(s).\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_POPULATES': | ||
return done( | ||
flaverr( | ||
{ | ||
name: 'UsageError' | ||
}, | ||
new Error( | ||
'Invalid populate(s).\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
return done(undefined, undefined); | ||
case 'E_NOOP': | ||
return done(undefined, undefined); | ||
default: | ||
return done(e); | ||
} | ||
} // >-• | ||
default: | ||
return done(e); | ||
} | ||
} // >-• | ||
// ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╔╗ ╔═╗╔═╗╔═╗╦═╗╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ├─┤├─┤│││ │││ ├┤ ╠╩╗║╣ ╠╣ ║ ║╠╦╝║╣ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╚═╝╚═╝╚ ╚═╝╩╚═╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Determine what to do about running any lifecycle callbacks | ||
(function _maybeRunBeforeLC(proceed){ | ||
// ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╔╗ ╔═╗╔═╗╔═╗╦═╗╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ├─┤├─┤│││ │││ ├┤ ╠╩╗║╣ ╠╣ ║ ║╠╦╝║╣ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╚═╝╚═╝╚ ╚═╝╩╚═╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// 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); | ||
}//-• | ||
// 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); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: This is where the `beforeFindOne()` lifecycle callback would go | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
return proceed(undefined, query); | ||
})(function _afterPotentiallyRunningBeforeLC(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
})(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); | ||
// ================================================================================ | ||
// ================================================================================ | ||
// 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); | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// 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(new Error( | ||
'More than one matching record found for `.findOne()`:\n'+ | ||
'```\n'+ | ||
_.pluck(populatedRecords, WLModel.primaryKey)+'\n'+ | ||
'```\n'+ | ||
'\n'+ | ||
'Criteria used:\n'+ | ||
'```\n'+ | ||
util.inspect(query.criteria,{depth:5})+''+ | ||
'```' | ||
)); | ||
}//-• | ||
// If more than one matching record was found, then consider this an error. | ||
if (populatedRecords.length > 1) { | ||
return done(new Error( | ||
'More than one matching record found for `.findOne()`:\n'+ | ||
'```\n'+ | ||
_.pluck(populatedRecords, WLModel.primaryKey)+'\n'+ | ||
'```\n'+ | ||
'\n'+ | ||
'Criteria used:\n'+ | ||
'```\n'+ | ||
util.inspect(query.criteria,{depth:5})+''+ | ||
'```' | ||
)); | ||
}//-• | ||
// Check and see if we actually found a record. | ||
var thePopulatedRecord = _.first(populatedRecords); | ||
// 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. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// 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) { | ||
// 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); } | ||
// 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){ | ||
// ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ├─┤├─┤│││ │││ ├┤ ╠═╣╠╣ ║ ║╣ ╠╦╝ │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╩ ╩╚ ╩ ╚═╝╩╚═ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
(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); | ||
}//-• | ||
// 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); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: This is where the `afterFindOne()` lifecycle callback would go | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
return proceed(undefined, thePopulatedRecord); | ||
})(function _afterPotentiallyRunningAfterLC(err, thePopulatedRecord){ | ||
if (err) { return done(err); } | ||
})(function _afterPotentiallyRunningAfterLC(err, thePopulatedRecord){ | ||
if (err) { return done(err); } | ||
// All done. | ||
return done(undefined, thePopulatedRecord); | ||
// All done. | ||
return done(undefined, thePopulatedRecord); | ||
});//</ self-calling function to handle "after" lifecycle callback > | ||
}); //</ helpFind() > | ||
}); //</ self-calling function to handle "before" lifecycle callback > | ||
});//</ 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> | ||
}; |
@@ -7,8 +7,17 @@ /** | ||
var flaverr = require('flaverr'); | ||
// var buildOmen = require('../utils/query/build-omen'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var parley = require('parley'); | ||
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('findOrCreate'); | ||
/** | ||
* findOrCreate() | ||
@@ -36,3 +45,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -44,3 +53,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -62,4 +71,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function findOrCreate( /* criteria?, newRecord?, done?, meta? */ ) { | ||
module.exports = function findOrCreate( /* criteria?, newRecord?, explicitCbMaybe?, meta? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -70,5 +82,12 @@ var WLModel = this; | ||
// // TODO: | ||
// // Build an omen for potential use in an asynchronous callback below. | ||
// var omen = buildOmen(findOrCreate); | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Potentially build an omen here for potential use in an | ||
// asynchronous callback below if/when an error occurs. This would | ||
// provide for a better stack trace, since it would be based off of | ||
// the original method call, rather than containing extra stack entries | ||
// from various utilities calling each other within Waterline itself. | ||
// | ||
// > Note that it'd need to be passed in to the other model methods that | ||
// > get called internally. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -90,37 +109,25 @@ // Build query w/ initial, universal keys. | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (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; | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// Handle first argument: | ||
// | ||
// • findOrCreate(criteria, ...) | ||
query.criteria = args[0]; | ||
// • findOrCreate(criteria, newRecord, explicitCbMaybe, ...) | ||
query.criteria = args[0]; | ||
query.newRecord = args[1]; | ||
explicitCbMaybe = args[2]; | ||
query.meta = args[3]; | ||
// Handle second argument: | ||
// | ||
// • findOrCreate(criteria, newRecord) | ||
query.newRecord = args[1]; | ||
done = args[2]; | ||
_meta = args[3]; | ||
// Fold in `_meta`, if relevant. | ||
if (_meta) { | ||
query.meta = _meta; | ||
} // >- | ||
})(); | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -146,118 +153,147 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, findOrCreate, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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' }, | ||
new Error( | ||
'Invalid criteria.\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_NEW_RECORDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid new record(s).\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
// If the criteria is deemed to be a no-op, then normalize it into a standard format. | ||
// This way, it will continue to represent a no-op as we proceed below, so the `findOne()` | ||
// call will also come back with an E_NOOP, and so then it will go on to do a `.create()`. | ||
// And most importantly, this way we don't have to worry about the case where the no-op | ||
// was caused by an edge case like `false` (we need to be able to munge the criteria -- | ||
// i.e. deleting the `limit`). | ||
var STD_NOOP_CRITERIA = { where: { or: [] } }; | ||
query.criteria = STD_NOOP_CRITERIA; | ||
break; | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// 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' }, | ||
new Error( | ||
'Invalid criteria.\n' + | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
default: | ||
return done(e); | ||
} | ||
}// >-• | ||
case 'E_INVALID_NEW_RECORDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid new record(s).\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
// If the criteria is deemed to be a no-op, then normalize it into a standard format. | ||
// This way, it will continue to represent a no-op as we proceed below, so the `findOne()` | ||
// call will also come back with an E_NOOP, and so then it will go on to do a `.create()`. | ||
// And most importantly, this way we don't have to worry about the case where the no-op | ||
// was caused by an edge case like `false` (we need to be able to munge the criteria -- | ||
// i.e. deleting the `limit`). | ||
var STD_NOOP_CRITERIA = { where: { or: [] } }; | ||
query.criteria = STD_NOOP_CRITERIA; | ||
break; | ||
default: | ||
return done(e); | ||
} | ||
}// >-• | ||
// Remove the `limit` clause that may have been automatically attached above. | ||
// (This is so that the findOne query is valid.) | ||
delete query.criteria.limit; | ||
// Remove the `limit`, `skip`, and `sort` clauses so that our findOne query is valid. | ||
// (This is because they were automatically attached above.) | ||
delete query.criteria.limit; | ||
delete query.criteria.skip; | ||
delete query.criteria.sort; | ||
// ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬┌┐┌┌┬┐ ┌─┐┌┐┌┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ ├┤ ││││ ││ │ ││││├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └ ┴┘└┘─┴┘ └─┘┘└┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// Note that we pass in `meta` here, which ensures we're on the same db connection. | ||
// (provided one was explicitly passed in!) | ||
WLModel.findOne(query.criteria, function _afterPotentiallyFinding(err, foundRecord) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// Note that we pass through a flag as the third argument to our callback, | ||
// indicating whether a new record was created. | ||
if (foundRecord) { | ||
return done(undefined, foundRecord, false); | ||
} | ||
// ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬┌┐┌┌┬┐ ┌─┐┌┐┌┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ ├┤ ││││ ││ │ ││││├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └ ┴┘└┘─┴┘ └─┘┘└┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// Note that we pass in `meta` here, which ensures we're on the same db connection. | ||
// (provided one was explicitly passed in!) | ||
WLModel.findOne(query.criteria, function _afterPotentiallyFinding(err, foundRecord) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// So that the create query is valid, check if the primary key value was | ||
// automatically set to `null` by FS2Q (i.e. because it was unspecified.) | ||
// And if so, remove it. | ||
// | ||
// > IWMIH, we know this was automatic because, if `null` had been | ||
// > specified explicitly, it would have already caused an error in | ||
// > our call to FS2Q above (`null` is NEVER a valid PK value) | ||
var pkAttrName = WLModel.primaryKey; | ||
var wasPKValueCoercedToNull = _.isNull(query.newRecord[pkAttrName]); | ||
if (wasPKValueCoercedToNull) { | ||
delete query.newRecord[pkAttrName]; | ||
} | ||
// Note that we pass through a flag as the third argument to our callback, | ||
// indicating whether a new record was created. | ||
if (foundRecord) { | ||
return done(undefined, foundRecord, false); | ||
} | ||
// ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ │ ├┬┘├┤ ├─┤ │ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
WLModel.create(query.newRecord, function _afterCreating(err, createdRecord) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// So that the create query is valid, check if the primary key value was | ||
// automatically set to `null` by FS2Q (i.e. because it was unspecified.) | ||
// And if so, remove it. | ||
// | ||
// > IWMIH, we know this was automatic because, if `null` had been | ||
// > specified explicitly, it would have already caused an error in | ||
// > our call to FS2Q above (`null` is NEVER a valid PK value) | ||
var pkAttrName = WLModel.primaryKey; | ||
var wasPKValueCoercedToNull = _.isNull(query.newRecord[pkAttrName]); | ||
if (wasPKValueCoercedToNull) { | ||
delete query.newRecord[pkAttrName]; | ||
} | ||
// Pass the newly-created record to our callback. | ||
// > Note we set the `wasCreated` flag to `true` in this case. | ||
return done(undefined, createdRecord, true); | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
// that also has `fetch: true`. | ||
var modifiedMeta = _.extend({}, query.meta || {}, { fetch: true }); | ||
}, query.meta);//</.create()> | ||
}, query.meta);//</.findOne()> | ||
// ╔═╗═╗ ╦╔═╗╔═╗╦ ╦╔╦╗╔═╗ ┌─┐┬─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ║╣ ╔╩╦╝║╣ ║ ║ ║ ║ ║╣ │ ├┬┘├┤ ├─┤ │ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚═╝╩ ╚═╚═╝╚═╝╚═╝ ╩ ╚═╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
WLModel.create(query.newRecord, function _afterCreating(err, createdRecord) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: Instead of preventing projections (`omit`/`select`) for findOrCreate, | ||
// instead allow them and just modify the newly created record after the fact | ||
// (i.e. trim properties in-memory). | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Pass the newly-created record to our callback. | ||
// > Note we set the `wasCreated` flag to `true` in this case. | ||
return done(undefined, createdRecord, true); | ||
}, modifiedMeta);//</.create()> | ||
}, query.meta);//</.findOne()> | ||
}, | ||
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> | ||
}; |
@@ -13,6 +13,7 @@ /** | ||
var processAllRecords = require('../utils/query/process-all-records'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module dependencies | ||
* Module constants | ||
*/ | ||
@@ -46,3 +47,3 @@ | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -54,3 +55,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -72,4 +73,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function find( /* criteria?, populates?, done?, meta? */ ) { | ||
module.exports = function find( /* 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. | ||
@@ -104,40 +108,34 @@ var WLModel = this; | ||
// | ||
// > Note that we define `args` so that we can insulate access | ||
// > to the arguments provided to this function. | ||
var args = arguments; | ||
(function _handleVariadicUsage() { | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// The metadata container, if one was provided. | ||
var _meta; | ||
// Handle first argument: | ||
// | ||
// • find(criteria, ...) | ||
// • find(explicitCbMaybe, ...) | ||
if (args.length >= 1 && _.isFunction(args[0])) { | ||
explicitCbMaybe = args[0]; | ||
query.meta = args[1]; | ||
} | ||
// • find(criteria, explicitCbMaybe, ...) | ||
else if (args.length >= 2 && _.isFunction(args[1])) { | ||
query.criteria = args[0]; | ||
explicitCbMaybe = args[1]; | ||
query.meta = args[2]; | ||
} | ||
// • find() | ||
// • find(criteria) | ||
// • find(criteria, populates, ...) | ||
else { | ||
query.criteria = args[0]; | ||
query.populates = args[1]; | ||
explicitCbMaybe = args[2]; | ||
query.meta = args[3]; | ||
} | ||
// Handle double meaning of second argument: | ||
// | ||
// • find(..., 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]; | ||
} | ||
// • find(..., explicitCbMaybe, _meta) | ||
else { | ||
explicitCbMaybe = args[1]; | ||
_meta = args[2]; | ||
} | ||
// Fold in `_meta`, if relevant. | ||
if (_meta) { | ||
query.meta = _meta; | ||
} // >- | ||
})(); | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -163,3 +161,3 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// If a callback function was not specified, then build a new `Deferred` and bail now. | ||
// If a callback function was not specified, then build a new Deferred and bail now. | ||
// | ||
@@ -194,3 +192,4 @@ // > This method will be called AGAIN automatically when the Deferred is executed. | ||
return done( | ||
flaverr({ | ||
flaverr( | ||
{ | ||
name: 'UsageError' | ||
@@ -208,3 +207,4 @@ }, | ||
return done( | ||
flaverr({ | ||
flaverr( | ||
{ | ||
name: 'UsageError' | ||
@@ -318,3 +318,3 @@ }, | ||
// Make sure `_wlQueryInfo` is always a dictionary. | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
@@ -321,0 +321,0 @@ |
@@ -7,8 +7,18 @@ /** | ||
var flaverr = require('flaverr'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var parley = require('parley'); | ||
var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
var helpRemoveFromCollection = require('../utils/collection-operations/help-remove-from-collection'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('removeFromCollection'); | ||
/** | ||
* removeFromCollection() | ||
@@ -36,3 +46,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -44,3 +54,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -73,4 +83,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function removeFromCollection(/* targetRecordIds?, collectionAttrName?, associatedIds?, done?, meta? */) { | ||
module.exports = function removeFromCollection(/* targetRecordIds?, collectionAttrName?, associatedIds?, explicitCbMaybe?, meta? */) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -87,2 +100,5 @@ var WLModel = this; | ||
// from various utilities calling each other within Waterline itself. | ||
// | ||
// > Note that it'd need to be passed in to the other model methods that | ||
// > get called internally. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -106,9 +122,9 @@ | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (locate the `explicitCbMaybe` callback, and extend the `query` dictionary) | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback) | ||
// (locate the `explicitCbMaybe` callback) | ||
// | ||
@@ -134,12 +150,12 @@ // > Note that we define `args` so that we can insulate access | ||
// | ||
// • removeFromCollection(____, ____, associatedIds, done, _meta) | ||
// • removeFromCollection(____, ____, associatedIds, explicitCbMaybe, _meta) | ||
var is3rdArgArray = !_.isUndefined(args[2]); | ||
if (is3rdArgArray) { | ||
query.associatedIds = args[2]; | ||
done = args[3]; | ||
explicitCbMaybe = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • removeFromCollection(____, ____, done, _meta) | ||
// • removeFromCollection(____, ____, explicitCbMaybe, _meta) | ||
else { | ||
done = args[2]; | ||
explicitCbMaybe = args[2]; | ||
_meta = args[3]; | ||
@@ -155,3 +171,2 @@ } | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -177,101 +192,115 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, removeFromCollection, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.removeFromCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.removeFromCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.removeFromCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.removeFromCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.removeFromCollection()` should be '+ | ||
'the ID (or IDs) of associated records to remove.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.removeFromCollection()` should be '+ | ||
'the ID (or IDs) of associated records to remove.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids or empty array of associated ids (members) | ||
case 'E_NOOP': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpRemoveFromCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids or empty array of associated ids (members) | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
});//</helpRemoveFromCollection> | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
}, | ||
} | ||
} // >-• | ||
explicitCbMaybe, | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpRemoveFromCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
_.extend(DEFERRED_METHODS, { | ||
});//</helpRemoveFromCollection> | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
}) | ||
);//</parley> | ||
}; | ||
@@ -7,8 +7,18 @@ /** | ||
var flaverr = require('flaverr'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var parley = require('parley'); | ||
var forgeStageTwoQuery = require('../utils/query/forge-stage-two-query'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
var helpReplaceCollection = require('../utils/collection-operations/help-replace-collection'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('replaceCollection'); | ||
/** | ||
* replaceCollection() | ||
@@ -34,3 +44,3 @@ * | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -42,3 +52,3 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
@@ -71,4 +81,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
module.exports = function replaceCollection(/* targetRecordIds?, collectionAttrName?, associatedIds?, done?, meta? */) { | ||
module.exports = function replaceCollection(/* targetRecordIds?, collectionAttrName?, associatedIds?, explicitCbMaybe?, meta? */) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -85,2 +98,5 @@ var WLModel = this; | ||
// from various utilities calling each other within Waterline itself. | ||
// | ||
// > Note that it'd need to be passed in to the other model methods that | ||
// > get called internally. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -103,9 +119,9 @@ | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (locate the `explicitCbMaybe` callback, and extend the `query` dictionary) | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback) | ||
// (locate the `explicitCbMaybe` callback) | ||
// | ||
@@ -131,12 +147,12 @@ // > Note that we define `args` so that we can insulate access | ||
// | ||
// • replaceCollection(____, ____, associatedIds, done, _meta) | ||
// • replaceCollection(____, ____, associatedIds, explicitCbMaybe, _meta) | ||
var is3rdArgArray = !_.isUndefined(args[2]); | ||
if (is3rdArgArray) { | ||
query.associatedIds = args[2]; | ||
done = args[3]; | ||
explicitCbMaybe = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • replaceCollection(____, ____, done, _meta) | ||
// • replaceCollection(____, ____, explicitCbMaybe, _meta) | ||
else { | ||
done = args[2]; | ||
explicitCbMaybe = args[2]; | ||
_meta = args[3]; | ||
@@ -153,3 +169,2 @@ } | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -175,100 +190,115 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, replaceCollection, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.replaceCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_TARGET_RECORD_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The target record ids (i.e. first argument) passed to `.replaceCollection()` '+ | ||
'should be the ID (or IDs) of target records whose collection will be modified.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.replaceCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_COLLECTION_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The collection attr name (i.e. second argument) to `.replaceCollection()` should '+ | ||
'be the name of a collection association from this model.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.replaceCollection()` should be '+ | ||
'the ID (or IDs) of associated records to use.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_INVALID_ASSOCIATED_IDS': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The associated ids (i.e. using `.members()`, or the third argument) passed to `.replaceCollection()` should be '+ | ||
'the ID (or IDs) of associated records to use.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids | ||
case 'E_NOOP': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpReplaceCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
// ^ tolerate no-ops -- i.e. empty array of target record ids | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
});//</helpReplaceCollection> | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
}, | ||
} | ||
} // >-• | ||
explicitCbMaybe, | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
helpReplaceCollection(query, orm, function (err) { | ||
if (err) { return done(err); } | ||
// IWMIH, everything worked! | ||
// > Note that we do not send back a result of any kind-- this it to reduce the likelihood | ||
// > writing userland code that relies undocumented/experimental output. | ||
return done(); | ||
_.extend(DEFERRED_METHODS, { | ||
});//</helpReplaceCollection> | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
}) | ||
);//</parley> | ||
}; |
@@ -9,7 +9,17 @@ /** | ||
var flaverr = require('flaverr'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var parley = require('parley'); | ||
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('stream'); | ||
/** | ||
* stream() | ||
@@ -44,7 +54,3 @@ * | ||
* | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -56,4 +62,8 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -83,4 +93,7 @@ * | ||
module.exports = function stream( /* criteria?, eachRecordFn?, moreQueryKeys?, done?, meta? */ ) { | ||
module.exports = function stream( /* criteria?, eachRecordFn?, explicitCbMaybe?, meta?, moreQueryKeys? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -97,2 +110,5 @@ var WLModel = this; | ||
// from various utilities calling each other within Waterline itself. | ||
// | ||
// > Note that it'd need to be passed in to the other model methods that | ||
// > get called internally. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -115,85 +131,44 @@ | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (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(){ | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// Additional query keys. | ||
var _moreQueryKeys; | ||
// The metadata container, if one was provided. | ||
var _meta; | ||
// Handle double meaning of first argument: | ||
// | ||
// • stream(criteria, ...) | ||
var is1stArgDictionary = (_.isObject(args[0]) && !_.isFunction(args[0]) && !_.isArray(args[0])); | ||
if (is1stArgDictionary) { | ||
query.criteria = args[0]; | ||
// • stream(eachRecordFn, ..., ..., ...) | ||
// • stream(eachRecordFn, explicitCbMaybe, ..., ...) | ||
if (args.length >= 1 && _.isFunction(args[0])) { | ||
query.eachRecordFn = args[0]; | ||
explicitCbMaybe = args[1]; | ||
query.meta = args[2]; | ||
if (args[3]) { | ||
_.extend(query, args[3]); | ||
} | ||
// • stream(eachRecordFn, ...) | ||
else { | ||
query.eachRecordFn = args[0]; | ||
} | ||
// • stream(criteria, ..., ..., ..., ...) | ||
// • stream(criteria, eachRecordFn, ..., ..., ...) | ||
// • stream() | ||
else { | ||
query.criteria = args[0]; | ||
query.eachRecordFn = args[1]; | ||
explicitCbMaybe = args[2]; | ||
query.meta = args[3]; | ||
if (args[4]) { | ||
_.extend(query, args[4]); | ||
} | ||
} | ||
// Handle double meaning of second argument: | ||
// | ||
// • stream(..., _moreQueryKeys, done, _meta) | ||
var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); | ||
if (is2ndArgDictionary) { | ||
_moreQueryKeys = args[1]; | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// • stream(..., eachRecordFn, ...) | ||
else { | ||
query.eachRecordFn = args[1]; | ||
} | ||
// Handle double meaning of third argument: | ||
// | ||
// • stream(..., ..., _moreQueryKeys, done, _meta) | ||
var is3rdArgDictionary = (_.isObject(args[2]) && !_.isFunction(args[2]) && !_.isArray(args[2])); | ||
if (is3rdArgDictionary) { | ||
_moreQueryKeys = args[2]; | ||
done = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • stream(..., ..., done, _meta) | ||
else { | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// Fold in `_moreQueryKeys`, if provided. | ||
// | ||
// > Userland is prevented from overriding any of the universal keys this way. | ||
if (_moreQueryKeys) { | ||
delete _moreQueryKeys.method; | ||
delete _moreQueryKeys.using; | ||
delete _moreQueryKeys.meta; | ||
_.extend(query, _moreQueryKeys); | ||
}//>- | ||
// Fold in `_meta`, if provided. | ||
if (_meta) { | ||
query.meta = _meta; | ||
}//>- | ||
})();//</self-calling function :: handleVariadicUsage()> | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -219,265 +194,279 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, stream, query); | ||
}//--• | ||
// 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... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_STREAM_ITERATEE': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Missing or invalid iteratee function for `.stream()`.\n'+ | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_POPULATES': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_INVALID_STREAM_ITERATEE': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid iteratee function passed in to `.stream()` via `.eachRecord()` or `.eachBatch()`.\n'+ | ||
'Details:\n' + | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
case 'E_NOOP': | ||
return done(); | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_POPULATES': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
case 'E_NOOP': | ||
return done(); | ||
} | ||
} //>-• | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} //>-• | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
// | ||
// When running a `.stream()`, Waterline grabs pages (batches) of like 30 records at a time. | ||
// This is not currently configurable. | ||
// | ||
// > If you have a use case for changing this page size (batch size) dynamically, please | ||
// > create an issue with a detailed explanation. Wouldn't be hard to add, we just | ||
// > haven't run across a need to change it yet. | ||
var BATCH_SIZE = 30; | ||
// A flag that will be set to true after we've reached the VERY last batch. | ||
var reachedLastBatch; | ||
// ┌┐┌┌─┐┬ ┬ ╔═╗╔═╗╔╦╗╦ ╦╔═╗╦ ╦ ╦ ╦ ┌┬┐┌─┐┬ ┬┌─ ┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌┬┐┌┐ ┌─┐ | ||
// ││││ ││││ ╠═╣║ ║ ║ ║╠═╣║ ║ ╚╦╝ │ ├─┤│ ├┴┐ │ │ │ │ ├─┤├┤ ││├┴┐└─┐ | ||
// ┘└┘└─┘└┴┘ ╩ ╩╚═╝ ╩ ╚═╝╩ ╩╩═╝╩═╝╩ ┴ ┴ ┴┴─┘┴ ┴ ┴ └─┘ ┴ ┴ ┴└─┘ ─┴┘└─┘└─┘ | ||
// | ||
// When running a `.stream()`, Waterline grabs pages (batches) of like 30 records at a time. | ||
// This is not currently configurable. | ||
// | ||
// > If you have a use case for changing this page size (batch size) dynamically, please | ||
// > create an issue with a detailed explanation. Wouldn't be hard to add, we just | ||
// > haven't run across a need to change it yet. | ||
var BATCH_SIZE = 30; | ||
// The index of the current batch. | ||
var i = 0; | ||
// A flag that will be set to true after we've reached the VERY last batch. | ||
var reachedLastBatch; | ||
// The index of the current batch. | ||
var i = 0; | ||
async.whilst(function _checkHasntReachedLastBatchYet(){ | ||
if (!reachedLastBatch) { return true; } | ||
else { return false; } | ||
},// ~∞%° | ||
function _beginBatchMaybe(next) { | ||
// 0 => 15 | ||
// 15 => 15 | ||
// 30 => 15 | ||
// 45 => 5 | ||
// 50 | ||
var numRecordsLeftUntilAbsLimit = query.criteria.limit - ( i*BATCH_SIZE ); | ||
var limitForThisBatch = Math.min(numRecordsLeftUntilAbsLimit, BATCH_SIZE); | ||
var skipForThisBatch = query.criteria.skip + ( i*BATCH_SIZE ); | ||
// |_initial offset + |_relative offset from end of previous batch | ||
async.whilst(function _checkHasntReachedLastBatchYet(){ | ||
if (!reachedLastBatch) { return true; } | ||
else { return false; } | ||
},// ~∞%° | ||
function _beginBatchMaybe(next) { | ||
// 0 => 15 | ||
// 15 => 15 | ||
// 30 => 15 | ||
// 45 => 5 | ||
// 50 | ||
var numRecordsLeftUntilAbsLimit = query.criteria.limit - ( i*BATCH_SIZE ); | ||
var limitForThisBatch = Math.min(numRecordsLeftUntilAbsLimit, BATCH_SIZE); | ||
var skipForThisBatch = query.criteria.skip + ( i*BATCH_SIZE ); | ||
// |_initial offset + |_relative offset from end of previous batch | ||
// If we've exceeded the absolute limit, then we go ahead and stop. | ||
if (limitForThisBatch <= 0) { | ||
reachedLastBatch = true; | ||
return next(); | ||
}//-• | ||
// Build the criteria + deferred object to do a `.find()` for this batch. | ||
var criteriaForThisBatch = { | ||
skip: skipForThisBatch, | ||
limit: limitForThisBatch, | ||
sort: query.criteria.sort, | ||
select: query.criteria.select, | ||
omit: query.criteria.omit, | ||
where: query.criteria.where | ||
}; | ||
// console.log('---iterating---'); | ||
// console.log('i:',i); | ||
// console.log(' BATCH_SIZE:',BATCH_SIZE); | ||
// console.log(' query.criteria.limit:',query.criteria.limit); | ||
// console.log(' query.criteria.skip:',query.criteria.skip); | ||
// console.log(' query.criteria.sort:',query.criteria.sort); | ||
// console.log(' query.criteria.where:',query.criteria.where); | ||
// console.log(' query.criteria.select:',query.criteria.select); | ||
// console.log(' query.criteria.omit:',query.criteria.omit); | ||
// console.log(' --'); | ||
// console.log(' criteriaForThisBatch.limit:',criteriaForThisBatch.limit); | ||
// console.log(' criteriaForThisBatch.skip:',criteriaForThisBatch.skip); | ||
// console.log(' criteriaForThisBatch.sort:',criteriaForThisBatch.sort); | ||
// console.log(' criteriaForThisBatch.where:',criteriaForThisBatch.where); | ||
// console.log(' criteriaForThisBatch.select:',criteriaForThisBatch.select); | ||
// console.log(' criteriaForThisBatch.omit:',criteriaForThisBatch.omit); | ||
// console.log('---•••••••••---'); | ||
var deferredForThisBatch = WLModel.find(criteriaForThisBatch); | ||
// If we've exceeded the absolute limit, then we go ahead and stop. | ||
if (limitForThisBatch <= 0) { | ||
reachedLastBatch = true; | ||
return next(); | ||
}//-• | ||
_.each(query.populates, function (assocCriteria, assocName){ | ||
deferredForThisBatch = deferredForThisBatch.populate(assocName, assocCriteria); | ||
}); | ||
// Build the criteria + deferred object to do a `.find()` for this batch. | ||
var criteriaForThisBatch = { | ||
skip: skipForThisBatch, | ||
limit: limitForThisBatch, | ||
sort: query.criteria.sort, | ||
select: query.criteria.select, | ||
omit: query.criteria.omit, | ||
where: query.criteria.where | ||
}; | ||
// console.log('---iterating---'); | ||
// console.log('i:',i); | ||
// console.log(' BATCH_SIZE:',BATCH_SIZE); | ||
// console.log(' query.criteria.limit:',query.criteria.limit); | ||
// console.log(' query.criteria.skip:',query.criteria.skip); | ||
// console.log(' query.criteria.sort:',query.criteria.sort); | ||
// console.log(' query.criteria.where:',query.criteria.where); | ||
// console.log(' query.criteria.select:',query.criteria.select); | ||
// console.log(' query.criteria.omit:',query.criteria.omit); | ||
// console.log(' --'); | ||
// console.log(' criteriaForThisBatch.limit:',criteriaForThisBatch.limit); | ||
// console.log(' criteriaForThisBatch.skip:',criteriaForThisBatch.skip); | ||
// console.log(' criteriaForThisBatch.sort:',criteriaForThisBatch.sort); | ||
// console.log(' criteriaForThisBatch.where:',criteriaForThisBatch.where); | ||
// console.log(' criteriaForThisBatch.select:',criteriaForThisBatch.select); | ||
// console.log(' criteriaForThisBatch.omit:',criteriaForThisBatch.omit); | ||
// console.log('---•••••••••---'); | ||
var deferredForThisBatch = WLModel.find(criteriaForThisBatch); | ||
// Pass through `meta` so we're sure to use the same db connection | ||
// and settings (i.e. esp. relevant if we happen to be inside a transaction) | ||
deferredForThisBatch.meta(query.meta); | ||
_.each(query.populates, function (assocCriteria, assocName){ | ||
deferredForThisBatch = deferredForThisBatch.populate(assocName, assocCriteria); | ||
}); | ||
deferredForThisBatch.exec(function (err, batchOfRecords){ | ||
if (err) { return next(err); } | ||
// Pass through `meta` so we're sure to use the same db connection | ||
// and settings (i.e. esp. relevant if we happen to be inside a transaction) | ||
deferredForThisBatch.meta(query.meta); | ||
// If there were no records returned, then we have already reached the last batch of results. | ||
// (i.e. it was the previous batch-- since this batch was empty) | ||
// In this case, we'll set the `reachedLastBatch` flag and trigger our callback, | ||
// allowing `async.whilst()` to call _its_ callback, which will pass control back | ||
// to userland. | ||
if (batchOfRecords.length === 0) { | ||
reachedLastBatch = true; | ||
return next(); | ||
}// --• | ||
deferredForThisBatch.exec(function (err, batchOfRecords){ | ||
if (err) { return next(err); } | ||
// But otherwise, we need to go ahead and call the appropriate | ||
// iteratee for this batch. If it's eachBatchFn, we'll call it | ||
// once. If it's eachRecordFn, we'll call it once per record. | ||
(function _makeCallOrCallsToAppropriateIteratee(proceed){ | ||
// If there were no records returned, then we have already reached the last batch of results. | ||
// (i.e. it was the previous batch-- since this batch was empty) | ||
// In this case, we'll set the `reachedLastBatch` flag and trigger our callback, | ||
// allowing `async.whilst()` to call _its_ callback, which will pass control back | ||
// to userland. | ||
if (batchOfRecords.length === 0) { | ||
reachedLastBatch = true; | ||
return next(); | ||
}// --• | ||
// If an `eachBatchFn` iteratee was provided, we'll call it. | ||
// > At this point we already know it's a function, because | ||
// > we validated usage at the very beginning. | ||
if (query.eachBatchFn) { | ||
// But otherwise, we need to go ahead and call the appropriate | ||
// iteratee for this batch. If it's eachBatchFn, we'll call it | ||
// once. If it's eachRecordFn, we'll call it once per record. | ||
(function _makeCallOrCallsToAppropriateIteratee(proceed){ | ||
// Note that, if you try to call next() more than once in the iteratee, Waterline | ||
// logs a warning explaining what's up, ignoring all subsequent calls to next() | ||
// that occur after the first. | ||
var didIterateeAlreadyHalt; | ||
try { | ||
query.eachBatchFn(batchOfRecords, function (err) { | ||
if (err) { return proceed(err); } | ||
// If an `eachBatchFn` iteratee was provided, we'll call it. | ||
// > At this point we already know it's a function, because | ||
// > we validated usage at the very beginning. | ||
if (query.eachBatchFn) { | ||
if (didIterateeAlreadyHalt) { | ||
console.warn( | ||
'Warning: The per-batch iteratee provided to `.stream()` triggered its callback \n'+ | ||
'again-- after already triggering it once! Please carefully check your iteratee\'s \n'+ | ||
'code to figure out why this is happening. (Ignoring this subsequent invocation...)' | ||
); | ||
return; | ||
}//-• | ||
// Note that, if you try to call next() more than once in the iteratee, Waterline | ||
// logs a warning explaining what's up, ignoring all subsequent calls to next() | ||
// that occur after the first. | ||
var didIterateeAlreadyHalt; | ||
try { | ||
query.eachBatchFn(batchOfRecords, function (err) { | ||
if (err) { return proceed(err); } | ||
didIterateeAlreadyHalt = true; | ||
if (didIterateeAlreadyHalt) { | ||
console.warn( | ||
'Warning: The per-batch iteratee provided to `.stream()` triggered its callback \n'+ | ||
'again-- after already triggering it once! Please carefully check your iteratee\'s \n'+ | ||
'code to figure out why this is happening. (Ignoring this subsequent invocation...)' | ||
); | ||
return; | ||
}//-• | ||
return proceed(); | ||
});//</ invoked per-batch iteratee > | ||
} catch (e) { return proceed(e); }//>-• | ||
didIterateeAlreadyHalt = true; | ||
return; | ||
}//_∏_. | ||
// Otherwise `eachRecordFn` iteratee must have been provided. | ||
// We'll call it once per record in this batch. | ||
// > We validated usage at the very beginning, so we know that | ||
// > one or the other iteratee must have been provided as a | ||
// > valid function if we made it here. | ||
async.eachSeries(batchOfRecords, function _eachRecordInBatch(record, next) { | ||
// Note that, if you try to call next() more than once in the iteratee, Waterline | ||
// logs a warning explaining what's up, ignoring all subsequent calls to next() | ||
// that occur after the first. | ||
var didIterateeAlreadyHalt; | ||
try { | ||
query.eachRecordFn(record, function (err) { | ||
if (err) { return next(err); } | ||
if (didIterateeAlreadyHalt) { | ||
console.warn( | ||
'Warning: The per-record iteratee provided to `.stream()` triggered its callback\n'+ | ||
'again-- after already triggering it once! Please carefully check your iteratee\'s\n'+ | ||
'code to figure out why this is happening. (Ignoring this subsequent invocation...)' | ||
); | ||
return; | ||
}//-• | ||
didIterateeAlreadyHalt = true; | ||
return next(); | ||
});//</ invoked per-record iteratee > | ||
} catch (e) { return next(e); } | ||
},// ~∞%° | ||
function _afterIteratingOverRecordsInBatch(err) { | ||
if (err) { return proceed(err); } | ||
return proceed(); | ||
});//</ invoked per-batch iteratee > | ||
} catch (e) { return proceed(e); }//>-• | ||
return; | ||
}//_∏_. | ||
});//</async.eachSeries()> | ||
})(function _afterCallingIteratee(err){ | ||
if (err) { | ||
// Otherwise `eachRecordFn` iteratee must have been provided. | ||
// We'll call it once per record in this batch. | ||
// > We validated usage at the very beginning, so we know that | ||
// > one or the other iteratee must have been provided as a | ||
// > valid function if we made it here. | ||
async.eachSeries(batchOfRecords, function _eachRecordInBatch(record, next) { | ||
// Note that, if you try to call next() more than once in the iteratee, Waterline | ||
// logs a warning explaining what's up, ignoring all subsequent calls to next() | ||
// that occur after the first. | ||
var didIterateeAlreadyHalt; | ||
try { | ||
query.eachRecordFn(record, function (err) { | ||
if (err) { return next(err); } | ||
// Since this `err` might have come from the userland iteratee, | ||
// we can't completely trust it. So check it out, and if it's | ||
// not one already, convert `err` into Error instance. | ||
if (!_.isError(err)) { | ||
if (_.isString(err)) { | ||
err = new Error(err); | ||
} | ||
else { | ||
err = new Error(util.inspect(err, {depth:5})); | ||
} | ||
}//>- | ||
if (didIterateeAlreadyHalt) { | ||
console.warn( | ||
'Warning: The per-record iteratee provided to `.stream()` triggered its callback\n'+ | ||
'again-- after already triggering it once! Please carefully check your iteratee\'s\n'+ | ||
'code to figure out why this is happening. (Ignoring this subsequent invocation...)' | ||
); | ||
return; | ||
}//-• | ||
return next(err); | ||
}//--• | ||
didIterateeAlreadyHalt = true; | ||
// Increment the batch counter. | ||
i++; | ||
return next(); | ||
// On to the next batch! | ||
return next(); | ||
});//</ invoked per-record iteratee > | ||
} catch (e) { return next(e); } | ||
});//</self-calling function :: process this batch by making either one call or multiple calls to the appropriate iteratee> | ||
},// ~∞%° | ||
function _afterIteratingOverRecordsInBatch(err) { | ||
if (err) { return proceed(err); } | ||
});//</deferredForThisBatch.exec()> | ||
return proceed(); | ||
},// ~∞%° | ||
function _afterAsyncWhilst(err) { | ||
if (err) { return done(err); }//-• | ||
});//</async.eachSeries()> | ||
// console.log('finished `.whilst()` successfully'); | ||
return done(); | ||
})(function _afterCallingIteratee(err){ | ||
if (err) { | ||
});//</async.whilst()> | ||
// Since this `err` might have come from the userland iteratee, | ||
// we can't completely trust it. So check it out, and if it's | ||
// not one already, convert `err` into Error instance. | ||
if (!_.isError(err)) { | ||
if (_.isString(err)) { | ||
err = new Error(err); | ||
} | ||
else { | ||
err = new Error(util.inspect(err, {depth:5})); | ||
} | ||
}//>- | ||
}, | ||
return next(err); | ||
}//--• | ||
// Increment the batch counter. | ||
i++; | ||
explicitCbMaybe, | ||
// On to the next batch! | ||
return next(); | ||
});//</self-calling function :: process this batch by making either one call or multiple calls to the appropriate iteratee> | ||
_.extend(DEFERRED_METHODS, { | ||
});//</deferredForThisBatch.exec()> | ||
// Provide access to this model for use in query modifier methods. | ||
_WLModel: WLModel, | ||
},// ~∞%° | ||
function _afterAsyncWhilst(err) { | ||
if (err) { return done(err); }//-• | ||
// Set up initial query metadata. | ||
_wlQueryInfo: query, | ||
// console.log('finished `.whilst()` successfully'); | ||
return done(); | ||
}) | ||
});//</async.whilst()> | ||
);//</parley> | ||
@@ -489,2 +478,3 @@ }; | ||
/** | ||
@@ -502,1 +492,6 @@ * ad hoc demonstration... | ||
// Or using `sails console` in a sample app: | ||
// ``` | ||
// Product.stream({where: {luckyNumber: 29}}).eachBatch(function(record, next){console.log('batch:', record); return next(); }).then(function(){ console.log('ok.', arguments); }).catch(function(){ console.log('uh oh!!!!', arguments); }) | ||
// ``` |
@@ -7,2 +7,3 @@ /** | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
@@ -12,6 +13,15 @@ var forgeAdapterError = require('../utils/query/forge-adapter-error'); | ||
var forgeStageThreeQuery = require('../utils/query/forge-stage-three-query'); | ||
var Deferred = require('../utils/query/deferred'); | ||
var getQueryModifierMethods = require('../utils/query/get-query-modifier-methods'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
/** | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('sum'); | ||
/** | ||
* sum() | ||
@@ -43,7 +53,3 @@ * | ||
* | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @param {Function?} done | ||
* @param {Function?} explicitCbMaybe | ||
* Callback function to run when query has either finished successfully or errored. | ||
@@ -55,4 +61,8 @@ * (If unspecified, will return a Deferred object instead of actually doing anything.) | ||
* | ||
* @returns {Ref?} Deferred object if no `done` callback was provided | ||
* @param {Dictionary} moreQueryKeys | ||
* For internal use. | ||
* (A dictionary of query keys.) | ||
* | ||
* @returns {Ref?} Deferred object if no `explicitCbMaybe` callback was provided | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -76,4 +86,7 @@ * | ||
module.exports = function sum( /* numericAttrName?, criteria?, moreQueryKeys?, done?, meta? */ ) { | ||
module.exports = function sum( /* numericAttrName?, criteria?, explicitCbMaybe?, meta?, moreQueryKeys? */ ) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -102,80 +115,37 @@ var WLModel = this; | ||
// The `done` callback, if one was provided. | ||
var done; | ||
// The `explicitCbMaybe` callback, if one was provided. | ||
var explicitCbMaybe; | ||
// Handle the various supported usage possibilities | ||
// (locate the `done` callback, and extend the `query` dictionary) | ||
// (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(){ | ||
// > Note that we define `args` to minimize the chance of this "variadics" code | ||
// > introducing any unoptimizable performance problems. For details, see: | ||
// > https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments | ||
// > •=> `.length` is just an integer, this doesn't leak the `arguments` object itself | ||
// > •=> `i` is always valid index in the arguments object | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; ++i) { | ||
args[i] = arguments[i]; | ||
} | ||
// Additional query keys. | ||
var _moreQueryKeys; | ||
// The metadata container, if one was provided. | ||
var _meta; | ||
// Handle first argument: | ||
// | ||
// • sum(numericAttrName, ...) | ||
// • sum(numericAttrName, explicitCbMaybe, ..., ...) | ||
if (args.length >= 2 && _.isFunction(args[1])) { | ||
query.numericAttrName = args[0]; | ||
explicitCbMaybe = args[1]; | ||
query.meta = args[2]; | ||
if (args[3]) { _.extend(query, args[3]); } | ||
} | ||
// • sum(numericAttrName, criteria, ..., ..., ...) | ||
else { | ||
query.numericAttrName = args[0]; | ||
query.criteria = args[1]; | ||
explicitCbMaybe = args[2]; | ||
query.meta = args[3]; | ||
if (args[4]) { _.extend(query, args[4]); } | ||
} | ||
// Handle double meaning of second argument: | ||
// | ||
// • sum(..., criteria, done, _meta) | ||
var is2ndArgDictionary = (_.isObject(args[1]) && !_.isFunction(args[1]) && !_.isArray(args[1])); | ||
if (is2ndArgDictionary) { | ||
query.criteria = args[1]; | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// • sum(..., done, _meta) | ||
else { | ||
done = args[1]; | ||
_meta = args[2]; | ||
} | ||
// Handle double meaning of third argument: | ||
// | ||
// • sum(..., ..., _moreQueryKeys, done, _meta) | ||
var is3rdArgDictionary = (_.isObject(args[2]) && !_.isFunction(args[2]) && !_.isArray(args[2])); | ||
if (is3rdArgDictionary) { | ||
_moreQueryKeys = args[2]; | ||
done = args[3]; | ||
_meta = args[4]; | ||
} | ||
// • sum(..., ..., done, _meta) | ||
else { | ||
done = args[2]; | ||
_meta = args[3]; | ||
} | ||
// Fold in `_moreQueryKeys`, if relevant. | ||
// | ||
// > Userland is prevented from overriding any of the universal keys this way. | ||
if (_moreQueryKeys) { | ||
delete _moreQueryKeys.method; | ||
delete _moreQueryKeys.using; | ||
delete _moreQueryKeys.meta; | ||
_.extend(query, _moreQueryKeys); | ||
} // >- | ||
// Fold in `_meta`, if relevant. | ||
if (_meta) { | ||
query.meta = _meta; | ||
} // >- | ||
})(); | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -201,94 +171,109 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// 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. | ||
if (!done) { | ||
return new Deferred(WLModel, sum, query); | ||
} // --• | ||
// 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 a callback was specified. | ||
// So... | ||
// | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
// 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) { | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_NUMERIC_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The numeric attr name (i.e. first argument) to `.sum()` should '+ | ||
'be the name of an attribute in this model which is defined with `type: \'number\'`.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
// ^ custom override for the standard usage error. Note that we use `.details` to get at | ||
// the underlying, lower-level error message (instead of logging redundant stuff from | ||
// the envelope provided by the default error msg.) | ||
case 'E_INVALID_NUMERIC_ATTR_NAME': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'The numeric attr name (i.e. first argument) to `.sum()` should '+ | ||
'be the name of an attribute in this model which is defined with `type: \'number\'`.\n'+ | ||
'Details:\n'+ | ||
' ' + e.details + '\n' | ||
) | ||
) | ||
); | ||
// ^ custom override for the standard usage error. Note that we use `.details` to get at | ||
// the underlying, lower-level error message (instead of logging redundant stuff from | ||
// the envelope provided by the default error msg.) | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_INVALID_CRITERIA': | ||
case 'E_INVALID_META': | ||
return done(e); | ||
// ^ when the standard usage error is good enough as-is, without any further customization | ||
case 'E_NOOP': | ||
return done(undefined, 0); | ||
case 'E_NOOP': | ||
return done(undefined, 0); | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
default: | ||
return done(e); | ||
// ^ when an internal, miscellaneous, or unexpected error occurs | ||
} | ||
} // >-• | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
try { | ||
query = forgeStageThreeQuery({ | ||
stageTwoQuery: query, | ||
identity: modelIdentity, | ||
transformer: WLModel._transformer, | ||
originalModels: orm.collections | ||
}); | ||
} catch (e) { return done(e); } | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.sum) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.sum) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
adapter.sum(WLModel.datastore, query, function _afterTalkingToAdapter(err, sum) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'sum', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
adapter.sum(WLModel.datastore, query, function _afterTalkingToAdapter(err, sum) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'sum', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
return done(undefined, sum); | ||
return done(undefined, sum); | ||
});//</adapter.sum()> | ||
});//</adapter.sum()> | ||
}, | ||
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> | ||
}; |
/** | ||
* Module Dependencies | ||
* Module dependencies | ||
*/ | ||
@@ -9,25 +9,75 @@ | ||
var flaverr = require('flaverr'); | ||
var parley = require('parley'); | ||
var buildOmen = require('../utils/query/build-omen'); | ||
var Deferred = require('../utils/query/deferred'); | ||
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'); | ||
/** | ||
* Update all records matching criteria | ||
* Module constants | ||
*/ | ||
var DEFERRED_METHODS = getQueryModifierMethods('update'); | ||
/** | ||
* update() | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* FUTURE: when time allows, update these fireworks up here to match the other methods | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* Update records that match the specified criteria, patching them with | ||
* the provided values. | ||
* | ||
* ``` | ||
* // Forgive all debts: Zero out bank accounts with less than $0 in them. | ||
* BankAccount.update().where({ | ||
* balance: { '<': 0 } | ||
* }).set({ | ||
* balance: 0 | ||
* }).exec(function(err) { | ||
* // ... | ||
* }); | ||
* ``` | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* | ||
* Usage without deferred object: | ||
* ================================================ | ||
* | ||
* @param {Dictionary} criteria | ||
* | ||
* @param {Dictionary} valuesToSet | ||
* @param {Function} done | ||
* @returns Deferred object if no callback | ||
* | ||
* @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?} valuesToSet | ||
* | ||
* @qkey {Dictionary?} meta | ||
* @qkey {String} using | ||
* @qkey {String} method | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
*/ | ||
module.exports = function update(criteria, valuesToSet, done, metaContainer) { | ||
module.exports = function update(criteria, valuesToSet, explicitCbMaybe, metaContainer) { | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -42,3 +92,12 @@ var WLModel = this; | ||
// Build initial query. | ||
var query = { | ||
method: 'update', | ||
using: modelIdentity, | ||
criteria: criteria, | ||
valuesToSet: valuesToSet, | ||
meta: metaContainer | ||
}; | ||
// ██╗ ██╗ █████╗ ██████╗ ██╗ █████╗ ██████╗ ██╗ ██████╗███████╗ | ||
@@ -51,10 +110,7 @@ // ██║ ██║██╔══██╗██╔══██╗██║██╔══██╗██╔══██╗██║██╔════╝██╔════╝ | ||
// | ||
// FUTURE: when time allows, update this to match the "VARIADICS" format | ||
// used in the other model methods. | ||
// N/A | ||
// (there are no out-of-order, optional arguments) | ||
if (typeof criteria === 'function') { | ||
done = criteria; | ||
criteria = null; | ||
} | ||
// ██████╗ ███████╗███████╗███████╗██████╗ | ||
@@ -80,250 +136,264 @@ // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗ | ||
// └─ ┴└ ┴└─└─┘┴─┘└─┘ └┘ ┴ ┴┘└┘ ┴ ─┘ | ||
// Return Deferred or pass to adapter | ||
if (typeof done !== 'function') { | ||
return new Deferred(WLModel, WLModel.update, { | ||
method: 'update', | ||
criteria: criteria, | ||
valuesToSet: valuesToSet | ||
}); | ||
} | ||
// 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){ | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
// This ensures a normalized format. | ||
var query = { | ||
method: 'update', | ||
using: modelIdentity, | ||
criteria: criteria, | ||
valuesToSet: valuesToSet, | ||
meta: metaContainer | ||
}; | ||
// Otherwise, IWMIH, we know that a callback was specified. | ||
// So... | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_CRITERIA': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid criteria.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
// ███████╗██╗ ██╗███████╗ ██████╗██╗ ██╗████████╗███████╗ | ||
// ██╔════╝╚██╗██╔╝██╔════╝██╔════╝██║ ██║╚══██╔══╝██╔════╝ | ||
// █████╗ ╚███╔╝ █████╗ ██║ ██║ ██║ ██║ █████╗ | ||
// ██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══╝ | ||
// ███████╗██╔╝ ██╗███████╗╚██████╗╚██████╔╝ ██║ ███████╗ | ||
// ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ | ||
case 'E_INVALID_VALUES_TO_SET': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Cannot perform update with the provided values.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ││││ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ └┴┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// | ||
// Forge a stage 2 query (aka logical protostatement) | ||
// This ensures a normalized format. | ||
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. `{ numRecordsUpdated: 0 }`) | ||
// > See: https://github.com/treelinehq/waterline-query-docs/blob/master/docs/results.md#update | ||
var noopResult = undefined; | ||
if (query.meta && query.meta.fetch) { | ||
noopResult = []; | ||
}//>- | ||
return done(undefined, noopResult); | ||
try { | ||
forgeStageTwoQuery(query, orm); | ||
} catch (e) { | ||
switch (e.code) { | ||
case 'E_INVALID_CRITERIA': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Invalid criteria.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
default: | ||
return done(e); | ||
} | ||
} | ||
case 'E_INVALID_VALUES_TO_SET': | ||
return done( | ||
flaverr( | ||
{ name: 'UsageError' }, | ||
new Error( | ||
'Cannot perform update with the provided values.\n'+ | ||
'Details:\n'+ | ||
' '+e.details+'\n' | ||
) | ||
) | ||
); | ||
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. `{ numRecordsUpdated: 0 }`) | ||
// > See: https://github.com/treelinehq/waterline-query-docs/blob/master/docs/results.md#update | ||
var noopResult = undefined; | ||
if (query.meta && query.meta.fetch) { | ||
noopResult = []; | ||
}//>- | ||
return done(undefined, noopResult); | ||
// ╦ ╦╔═╗╔╗╔╔╦╗╦ ╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠═╣║║║ ║║║ ║╣ BEFORE │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╩ ╩╝╚╝═╩╝╩═╝╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run the "before" lifecycle callback, if appropriate. | ||
(function(proceed) { | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run any of | ||
// the methods. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return proceed(undefined, query); | ||
} | ||
default: | ||
return done(e); | ||
} | ||
} | ||
if (!_.has(WLModel._callbacks, 'beforeUpdate')) { | ||
return proceed(undefined, query); | ||
} | ||
WLModel._callbacks.beforeUpdate(query.valuesToSet, function(err){ | ||
if (err) { return proceed(err); } | ||
return proceed(undefined, query); | ||
}); | ||
// ╦ ╦╔═╗╔╗╔╔╦╗╦ ╔═╗ ┬ ┬┌─┐┌─┐┌─┐┬ ┬┌─┐┬ ┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠═╣║║║ ║║║ ║╣ BEFORE │ │├┤ ├┤ │ └┬┘│ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╩ ╩╝╚╝═╩╝╩═╝╚═╝ ┴─┘┴└ └─┘└─┘ ┴ └─┘┴─┘└─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run the "before" lifecycle callback, if appropriate. | ||
(function(proceed) { | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run any of | ||
// the methods. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return proceed(undefined, query); | ||
} | ||
})(function(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
if (!_.has(WLModel._callbacks, 'beforeUpdate')) { | ||
return proceed(undefined, query); | ||
} | ||
// ================================================================================ | ||
// 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); | ||
// ================================================================================ | ||
WLModel._callbacks.beforeUpdate(query.valuesToSet, function(err){ | ||
if (err) { return proceed(err); } | ||
return proceed(undefined, query); | ||
}); | ||
})(function(err, query) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
// ================================================================================ | ||
// 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); | ||
// ================================================================================ | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.update) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┌┬┐┌─┐┌─┐┌─┐ ┌┬┐┬ ┬┬─┐┌─┐┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
// ╠╣ ║ ║╠╦╝║ ╦║╣ └─┐ │ ├─┤│ ┬├┤ │ ├─┤├┬┘├┤ ├┤ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╚ ╚═╝╩╚═╚═╝╚═╝ └─┘ ┴ ┴ ┴└─┘└─┘ ┴ ┴ ┴┴└─└─┘└─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
// 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); } | ||
adapter.update(WLModel.datastore, query, function _afterTalkingToAdapter(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'update', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
// ┌─┐┌─┐┌┐┌┌┬┐ ┌┬┐┌─┐ ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╦═╗ | ||
// └─┐├┤ │││ ││ │ │ │ ╠═╣ ║║╠═╣╠═╝ ║ ║╣ ╠╦╝ | ||
// └─┘└─┘┘└┘─┴┘ ┴ └─┘ ╩ ╩═╩╝╩ ╩╩ ╩ ╚═╝╩╚═ | ||
// Grab the appropriate adapter method and call it. | ||
var adapter = WLModel._adapter; | ||
if (!adapter.update) { | ||
return done(new Error('The adapter used by this model (`' + modelIdentity + '`) doesn\'t support the `'+query.method+'` method.')); | ||
} | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
adapter.update(WLModel.datastore, query, function _afterTalkingToAdapter(err, rawAdapterResult) { | ||
if (err) { | ||
err = forgeAdapterError(err, omen, 'update', modelIdentity, orm); | ||
return done(err); | ||
}//-• | ||
if (!_.isUndefined(rawAdapterResult) && _.isArray(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 `update` method. But it did! And since it\'s an array, displaying this\n'+ | ||
'warning to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
return done(); | ||
// ╔═╗╔╦╗╔═╗╔═╗ ╔╗╔╔═╗╦ ╦ ┬ ┬┌┐┌┬ ┌─┐┌─┐┌─┐ ╔═╗╔═╗╔╦╗╔═╗╦ ╦ ┌┬┐┌─┐┌┬┐┌─┐ ┬┌─┌─┐┬ ┬ | ||
// ╚═╗ ║ ║ ║╠═╝ ║║║║ ║║║║ │ │││││ ├┤ └─┐└─┐ ╠╣ ║╣ ║ ║ ╠═╣ │││├┤ │ ├─┤ ├┴┐├┤ └┬┘ | ||
// ╚═╝ ╩ ╚═╝╩ ╝╚╝╚═╝╚╩╝ooo └─┘┘└┘┴─┘└─┘└─┘└─┘ ╚ ╚═╝ ╩ ╚═╝╩ ╩ ┴ ┴└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ | ||
// ┬ ┬┌─┐┌─┐ ┌─┐┌─┐┌┬┐ ┌┬┐┌─┐ ┌┬┐┬─┐┬ ┬┌─┐ | ||
// │││├─┤└─┐ └─┐├┤ │ │ │ │ │ ├┬┘│ │├┤ | ||
// └┴┘┴ ┴└─┘ └─┘└─┘ ┴ ┴ └─┘ ┴ ┴└─└─┘└─┘ | ||
// If `fetch` was not enabled, return. | ||
if (!_.has(query.meta, 'fetch') || query.meta.fetch === false) { | ||
}//-• | ||
if (!_.isUndefined(rawAdapterResult) && _.isArray(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 `update` method. But it did! And since it\'s an array, displaying this\n'+ | ||
'warning to help avoid confusion and draw attention to the bug. Specifically, got:\n'+ | ||
util.inspect(rawAdapterResult, {depth:5})+'\n'+ | ||
'(Ignoring it and proceeding anyway...)'+'\n' | ||
); | ||
}//>- | ||
return done(); | ||
// 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 done(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 `update` method. But instead, got: '+ | ||
util.inspect(rawAdapterResult, {depth:5})+'' | ||
)); | ||
}//-• | ||
// Unserialize each record | ||
var transformedRecords; | ||
try { | ||
// Attempt to convert the column names in each record back into attribute names. | ||
transformedRecords = rawAdapterResult.map(function(record) { | ||
return WLModel._transformer.unserialize(record); | ||
}); | ||
} catch (e) { return done(e); } | ||
// 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 done(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 `update` method. But instead, got: '+ | ||
util.inspect(rawAdapterResult, {depth:5})+'' | ||
)); | ||
}//-• | ||
// 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 done(e); } | ||
// Unserialize each record | ||
var transformedRecords; | ||
try { | ||
// Attempt to convert the column names in each record back into attribute names. | ||
transformedRecords = rawAdapterResult.map(function(record) { | ||
return WLModel._transformer.unserialize(record); | ||
}); | ||
} catch (e) { return done(e); } | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ │ │├─┘ ││├─┤ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ └─┘┴ ─┴┘┴ ┴ ┴ └─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run "after" lifecycle callback AGAIN and AGAIN- once for each record. | ||
// ============================================================ | ||
// FUTURE: look into this | ||
// (we probably shouldn't call this again and again-- | ||
// plus what if `fetch` is not in use and you want to use an LC? | ||
// Then again- the right answer isn't immediately clear. And it | ||
// probably not worth breaking compatibility until we have a much | ||
// better solution) | ||
// ============================================================ | ||
async.each(transformedRecords, function _eachRecord(record, next) { | ||
// 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 done(e); } | ||
// 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, 'afterUpdate')) { | ||
return next(); | ||
} | ||
// ╔═╗╔═╗╔╦╗╔═╗╦═╗ ┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬ ┬ ┌┐ ┌─┐┌─┐┬┌─ | ||
// ╠═╣╠╣ ║ ║╣ ╠╦╝ │ │├─┘ ││├─┤ │ ├┤ │ ├─┤│ │ ├┴┐├─┤│ ├┴┐ | ||
// ╩ ╩╚ ╩ ╚═╝╩╚═ └─┘┴ ─┴┘┴ ┴ ┴ └─┘ └─┘┴ ┴┴─┘┴─┘└─┘┴ ┴└─┘┴ ┴ | ||
// Run "after" lifecycle callback AGAIN and AGAIN- once for each record. | ||
// ============================================================ | ||
// FUTURE: look into this | ||
// (we probably shouldn't call this again and again-- | ||
// plus what if `fetch` is not in use and you want to use an LC? | ||
// Then again- the right answer isn't immediately clear. And it | ||
// probably not worth breaking compatibility until we have a much | ||
// better solution) | ||
// ============================================================ | ||
async.each(transformedRecords, function _eachRecord(record, next) { | ||
// Otherwise run it. | ||
WLModel._callbacks.afterUpdate(record, function _afterMaybeRunningAfterUpdateForThisRecord(err) { | ||
if (err) { | ||
return next(err); | ||
} | ||
// If the `skipAllLifecycleCallbacks` meta flag was set, don't run any of | ||
// the methods. | ||
if (_.has(query.meta, 'skipAllLifecycleCallbacks') && query.meta.skipAllLifecycleCallbacks) { | ||
return next(); | ||
} | ||
return next(); | ||
}); | ||
// Skip "after" lifecycle callback, if not defined. | ||
if (!_.has(WLModel._callbacks, 'afterUpdate')) { | ||
return next(); | ||
} | ||
},// ~∞%° | ||
function _afterIteratingOverRecords(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
// Otherwise run it. | ||
WLModel._callbacks.afterUpdate(record, function _afterMaybeRunningAfterUpdateForThisRecord(err) { | ||
if (err) { | ||
return next(err); | ||
} | ||
return done(undefined, transformedRecords); | ||
return next(); | ||
}); | ||
});//</ async.each() -- ran "after" lifecycle callback on each record > | ||
});//</ adapter.update() > | ||
});//</ "before" lifecycle callback > | ||
},// ~∞%° | ||
function _afterIteratingOverRecords(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
return done(undefined, transformedRecords); | ||
});//</ async.each() -- ran "after" lifecycle callback on each record > | ||
});//</ adapter.update() > | ||
});//</ "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> | ||
}; |
@@ -8,2 +8,3 @@ /** | ||
var normalizeValueToSet = require('../utils/query/private/normalize-value-to-set'); | ||
var verifyModelMethodContext = require('../utils/query/verify-model-method-context'); | ||
@@ -110,2 +111,5 @@ | ||
// Verify `this` refers to an actual Sails/Waterline model. | ||
verifyModelMethodContext(this); | ||
// Set up a few, common local vars for convenience / familiarity. | ||
@@ -158,4 +162,4 @@ var orm = this.waterline; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// FUTURE: change this logic so that it works like it does for `.create()` | ||
// (instead of just working like it does for .update()) | ||
// FUTURE: expand this logic so that it can work like it does for `.create()` | ||
// (in addition or instead of just working like it does for .update()) | ||
// | ||
@@ -162,0 +166,0 @@ // That entails applying required and defaultsTo down here at the bottom, |
@@ -66,4 +66,4 @@ /** | ||
// Ensure the query skips lifecycle callbacks | ||
query.meta = query.meta || {}; | ||
query.meta.skipAllLifecycleCallbacks = true; | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
var modifiedMeta = _.extend({}, query.meta || {}, { skipAllLifecycleCallbacks: true }); | ||
@@ -157,3 +157,3 @@ | ||
// ╩╚═╚═╝╝╚╝ └─┘└└─┘└─┘┴└─ ┴ | ||
WLChild.createEach(joinRecords, cb, query.meta); | ||
WLChild.createEach(joinRecords, cb, modifiedMeta); | ||
@@ -197,4 +197,4 @@ return; | ||
WLChild.update(criteria, valuesToUpdate, cb, query.meta); | ||
WLChild.update(criteria, valuesToUpdate, cb, modifiedMeta); | ||
}; |
@@ -66,9 +66,4 @@ /** | ||
// Ensure the query skips lifecycle callbacks | ||
query.meta = query.meta || {}; | ||
query.meta.skipAllLifecycleCallbacks = true; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: change this b/c we can't safely do that ^^^ | ||
// (it destructively mutates the `meta` QK such that it could change the behavior of other pieces of this query) | ||
// Instead, we need to do something else-- probably use a shallow clone | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
var modifiedMeta = _.extend({}, query.meta || {}, { skipAllLifecycleCallbacks: true }); | ||
@@ -184,3 +179,3 @@ | ||
return next(); | ||
}, query.meta); | ||
}, modifiedMeta); | ||
@@ -237,4 +232,4 @@ },// ~∞%° | ||
}, query.meta);//</.update()> | ||
}, modifiedMeta);//</.update()> | ||
}; |
@@ -67,4 +67,4 @@ /** | ||
// Ensure the query skips lifecycle callbacks | ||
query.meta = query.meta || {}; | ||
query.meta.skipAllLifecycleCallbacks = true; | ||
// Build a modified shallow clone of the originally-provided `meta` | ||
var modifiedMeta = _.extend({}, query.meta || {}, { skipAllLifecycleCallbacks: true }); | ||
@@ -152,5 +152,13 @@ | ||
// for the target id's in the join table. | ||
var destroyQuery = {}; | ||
destroyQuery[parentReference] = query.targetRecordIds; | ||
var destroyQuery = { | ||
where: {} | ||
}; | ||
destroyQuery.where[parentReference] = { | ||
in: query.targetRecordIds | ||
}; | ||
// Don't worry about fetching | ||
modifiedMeta.fetch = false; | ||
// ╔╗ ╦ ╦╦╦ ╔╦╗ ┬┌┐┌┌─┐┌─┐┬─┐┌┬┐ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬ | ||
@@ -190,4 +198,4 @@ // ╠╩╗║ ║║║ ║║ ││││└─┐├┤ ├┬┘ │ │─┼┐│ │├┤ ├┬┘└┬┘ | ||
// ╩╚═╚═╝╝╚╝ └─┘┴└─└─┘┴ ┴ ┴ └─┘ └─┘└└─┘└─┘┴└─ ┴ | ||
WLChild.createEach(insertRecords, cb, query.meta); | ||
}, query.meta); | ||
WLChild.createEach(insertRecords, cb, modifiedMeta); | ||
}, modifiedMeta); | ||
@@ -225,3 +233,5 @@ return; | ||
nullOutCriteria.where[schemaDef.via] = query.targetRecordIds; | ||
nullOutCriteria.where[schemaDef.via] = { | ||
in: query.targetRecordIds | ||
}; | ||
@@ -274,3 +284,3 @@ // Build up the values to update | ||
WLChild.update(query.criteria, query.valuesToUpdate, next, query.meta); | ||
WLChild.update(query.criteria, query.valuesToUpdate, next, modifiedMeta); | ||
@@ -286,3 +296,3 @@ },// ~∞%° | ||
}); | ||
}, query.meta); | ||
}, modifiedMeta); | ||
}; |
@@ -71,8 +71,3 @@ /** | ||
if (!_.isString(PrimaryWLModel.datastore) || !_.isString(OtherWLModel.datastore)) { | ||
console.warn('TODO: Fix outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012) The `datastore` property should be a string, not an array.'); | ||
// ^^^TODO: instead of the above lines (^^^) replace it with the following lines: | ||
// ``` | ||
// assert(_.isString(PrimaryWLModel.datastore)); | ||
// assert(_.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!'); | ||
} | ||
@@ -106,7 +101,3 @@ | ||
if (!_.isString(JunctionWLModel.datastore)) { | ||
console.warn('TODO: outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012) The `datastore` property should be a string, not an array.'); | ||
// ^^^TODO: instead of the above lines (^^^) replace it with the following lines: | ||
// ``` | ||
// assert(_.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!'); | ||
} | ||
@@ -132,8 +123,3 @@ | ||
if (!_.isString(PrimaryWLModel.datastore)) { | ||
console.warn('TODO: outdated semantics (see https://github.com/balderdashy/waterline/commit/ecd3e1c8f05e27a3b0c1ea4f08a73a0b4ad83c07#commitcomment-20271012) The `datastore` property should be a string, not an array.'); | ||
relevantDatastoreName = _.first(PrimaryWLModel.datastore); | ||
// ^^^TODO: instead of the above two lines (^^^) replace it with the following lines: | ||
// ``` | ||
// assert(_.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!'); | ||
} | ||
@@ -140,0 +126,0 @@ |
@@ -103,3 +103,3 @@ /** | ||
try { | ||
s3Q.newRecord = transformer.serialize(s3Q.newRecord); | ||
s3Q.newRecord = transformer.serializeValues(s3Q.newRecord); | ||
} catch (e) { | ||
@@ -135,3 +135,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
try { | ||
record = transformer.serialize(record); | ||
record = transformer.serializeValues(record); | ||
} catch (e) { | ||
@@ -176,3 +176,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
try { | ||
s3Q.valuesToSet = transformer.serialize(s3Q.valuesToSet); | ||
s3Q.valuesToSet = transformer.serializeValues(s3Q.valuesToSet); | ||
} catch (e) { | ||
@@ -188,3 +188,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
try { | ||
s3Q.criteria.where = transformer.serialize(s3Q.criteria.where); | ||
s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); | ||
} catch (e) { | ||
@@ -236,3 +236,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
try { | ||
s3Q.criteria.where = transformer.serialize(s3Q.criteria.where); | ||
s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); | ||
} catch (e) { | ||
@@ -302,3 +302,2 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
var attributeName = populateAttribute; | ||
if (!attrDefToPopulate) { | ||
@@ -308,21 +307,6 @@ throw new Error('In ' + util.format('`.populate("%s")`', populateAttribute) + ', attempting to populate an attribute that doesn\'t exist'); | ||
if (_.has(attrDefToPopulate, 'columnName')) { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: Figure out why we're accessing `columnName` this way instead of on wlsSchema | ||
// (see https://github.com/balderdashy/waterline/commit/19889b7ee265e9850657ec2b4c7f3012f213a0ae#commitcomment-20668361) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
attributeName = attrDefToPopulate.columnName; | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: Instead of setting `attributeName` as the column name, use a different | ||
// variable. (Otherwise this gets super confusing to try and understand.) | ||
// | ||
// (Side note: Isn't the `schema` from WLS keyed on attribute name? If not, then | ||
// there is other code in Waterline using it incorrectly) | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Grab the key being populated from the original model definition to check | ||
// if it is a has many or belongs to. If it's a belongs_to the adapter needs | ||
// to know that it should replace the foreign key with the associated value. | ||
var parentAttr = originalModels[identity].schema[attributeName]; | ||
var parentAttr = originalModels[identity].schema[populateAttribute]; | ||
@@ -508,4 +492,4 @@ // Build the initial join object that will link this collection to either another collection | ||
// If the model's hasSchema value is set to false, remove the select | ||
if (model.hasSchema === false) { | ||
s3Q.criteria.select = undefined; | ||
if (model.hasSchema === false || s3Q.meta && s3Q.meta.skipExpandingDefaultSelectClause) { | ||
delete s3Q.criteria.select; | ||
} | ||
@@ -566,3 +550,3 @@ | ||
try { | ||
s3Q.criteria.where = transformer.serialize(s3Q.criteria.where); | ||
s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); | ||
} catch (e) { | ||
@@ -583,3 +567,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
lastJoin.criteria = lastJoin.criteria || {}; | ||
lastJoin.criteria = joinCollection._transformer.serialize(lastJoin.criteria); | ||
lastJoin.criteria = joinCollection._transformer.serializeCriteria(lastJoin.criteria); | ||
@@ -616,3 +600,3 @@ // Ensure the join select doesn't contain duplicates | ||
try { | ||
s3Q.criteria = transformer.serialize(s3Q.criteria); | ||
s3Q.criteria = transformer.serializeCriteria(s3Q.criteria); | ||
} catch (e) { | ||
@@ -630,3 +614,3 @@ throw flaverr('E_INVALID_RECORD', new Error( | ||
_tmpNumbericAttr[s3Q.numericAttrName] = ''; | ||
var processedNumericAttrName = transformer.serialize(_tmpNumbericAttr); | ||
var processedNumericAttrName = transformer.serializeValues(_tmpNumbericAttr); | ||
s3Q.numericAttrName = _.first(_.keys(processedNumericAttrName)); | ||
@@ -633,0 +617,0 @@ } catch (e) { |
@@ -68,5 +68,4 @@ /** | ||
eachRecord: function(iteratee) { | ||
if (this._wlQueryInfo.method !== 'stream') { | ||
throw new Error('Cannot chain `.eachRecord()` onto the `.'+this._wlQueryInfo.method+'()` method. The `.eachRecord()` method is only chainable to `.stream()`.'); | ||
} | ||
assert(this._wlQueryInfo.method === 'stream', 'Cannot chain `.eachRecord()` onto the `.'+this._wlQueryInfo.method+'()` method. The `.eachRecord()` method is only chainable to `.stream()`. (In fact, this shouldn\'t even be possible! So the fact that you are seeing this message at all is, itself, likely due to a bug in Waterline.)'); | ||
this._wlQueryInfo.eachRecordFn = iteratee; | ||
@@ -77,5 +76,4 @@ return this; | ||
eachBatch: function(iteratee) { | ||
if (this._wlQueryInfo.method !== 'stream') { | ||
throw new Error('Cannot chain `.eachBatch()` onto the `.'+this._wlQueryInfo.method+'()` method. The `.eachBatch()` method is only chainable to `.stream()`.'); | ||
} | ||
assert(this._wlQueryInfo.method === 'stream', 'Cannot chain `.eachRecord()` onto the `.'+this._wlQueryInfo.method+'()` method. The `.eachRecord()` method is only chainable to `.stream()`. (In fact, this shouldn\'t even be possible! So the fact that you are seeing this message at all is, itself, likely due to a bug in Waterline.)'); | ||
this._wlQueryInfo.eachBatchFn = iteratee; | ||
@@ -190,22 +188,6 @@ return this; | ||
populate: function(keyName, subcriteria) { | ||
var self = this; | ||
// TODO: replace this with an assertion now that it shouldn't be possible: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Prevent attempting to populate with methods where it is not allowed. | ||
// (Note that this is primarily enforced in FS2Q, but it is also checked here for now | ||
// due to an implementation detail in Deferred. FUTURE: eliminate this) | ||
var POPULATE_COMPATIBLE_METHODS = ['find', 'findOne', 'stream']; | ||
var isCompatibleWithPopulate = _.contains(POPULATE_COMPATIBLE_METHODS, this._wlQueryInfo.method); | ||
if (!isCompatibleWithPopulate) { | ||
throw new Error('Cannot chain `.populate()` onto the `.'+this._wlQueryInfo.method+'()` method.'); | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
assert(this._wlQueryInfo.method === 'find' || this._wlQueryInfo.method === 'findOne' || this._wlQueryInfo.method === 'stream', 'Cannot chain `.populate()` onto the `.'+this._wlQueryInfo.method+'()` method. (In fact, this shouldn\'t even be possible! So the fact that you are seeing this message at all is, itself, likely due to a bug in Waterline.)'); | ||
if (!keyName || !_.isString(keyName)) { | ||
throw new Error('Invalid usage for `.populate()` -- first argument should be the name of an assocation.'); | ||
} | ||
// Adds support for arrays into keyName so that a list of | ||
// populates can be passed | ||
// Backwards compatibility for arrays passed in as `keyName`. | ||
if (_.isArray(keyName)) { | ||
@@ -222,2 +204,3 @@ console.warn( | ||
); | ||
var self = this; | ||
_.each(keyName, function(populate) { | ||
@@ -229,2 +212,8 @@ self.populate(populate, subcriteria); | ||
// Verify that we're dealing with a semi-reasonable string. | ||
// (This is futher validated) | ||
if (!keyName || !_.isString(keyName)) { | ||
throw new Error('Invalid usage for `.populate()` -- first argument should be the name of an assocation.'); | ||
} | ||
// If this is the first time, make the `populates` query key an empty dictionary. | ||
@@ -253,62 +242,5 @@ if (_.isUndefined(this._wlQueryInfo.populates)) { | ||
var CRITERIA_Q_METHODS = { | ||
var PAGINATION_Q_METHODS = { | ||
/** | ||
* Add projections to the query. | ||
* | ||
* @param {Array} attributes to select | ||
* @returns {Query} | ||
*/ | ||
select: function(selectAttributes) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.select = selectAttributes; | ||
return this; | ||
}, | ||
/** | ||
* Add an omit clause to the query's criteria. | ||
* | ||
* @param {Array} attributes to select | ||
* @returns {Query} | ||
*/ | ||
omit: function(omitAttributes) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.omit = omitAttributes; | ||
return this; | ||
}, | ||
/** | ||
* Add a `where` clause to the query's criteria. | ||
* | ||
* @param {Dictionary} criteria to append | ||
* @returns {Query} | ||
*/ | ||
where: function(whereCriteria) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.where = whereCriteria; | ||
return this; | ||
}, | ||
/** | ||
* Add a `limit` clause to the query's criteria. | ||
@@ -477,22 +409,92 @@ * | ||
}, | ||
}; | ||
var PROJECTION_Q_METHODS = { | ||
// ██╗ ██╗███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ██████╗ ████████╗███████╗██████╗ | ||
// ██║ ██║████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝██╔══██╗ | ||
// ██║ ██║██╔██╗ ██║███████╗██║ ██║██████╔╝██████╔╝██║ ██║██████╔╝ ██║ █████╗ ██║ ██║ | ||
// ██║ ██║██║╚██╗██║╚════██║██║ ██║██╔═══╝ ██╔═══╝ ██║ ██║██╔══██╗ ██║ ██╔══╝ ██║ ██║ | ||
// ╚██████╔╝██║ ╚████║███████║╚██████╔╝██║ ██║ ╚██████╔╝██║ ██║ ██║ ███████╗██████╔╝ | ||
// ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═════╝ | ||
// | ||
// ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ███████╗ | ||
// ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗██╔════╝ | ||
// ██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║███████╗ | ||
// ██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║╚════██║ | ||
// ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝███████║ | ||
// ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ | ||
// | ||
/** | ||
* Add projections to the query. | ||
* | ||
* @param {Array} attributes to select | ||
* @returns {Query} | ||
*/ | ||
select: function(selectAttributes) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.select = selectAttributes; | ||
return this; | ||
}, | ||
/** | ||
* Add an omit clause to the query's criteria. | ||
* | ||
* @param {Array} attributes to select | ||
* @returns {Query} | ||
*/ | ||
omit: function(omitAttributes) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.omit = omitAttributes; | ||
return this; | ||
}, | ||
}; | ||
var FILTER_Q_METHODS = { | ||
/** | ||
* Add a `where` clause to the query's criteria. | ||
* | ||
* @param {Dictionary} criteria to append | ||
* @returns {Query} | ||
*/ | ||
where: function(whereCriteria) { | ||
if (!this._alreadyInitiallyExpandedCriteria) { | ||
this._wlQueryInfo.criteria = expandWhereShorthand(this._wlQueryInfo.criteria); | ||
this._alreadyInitiallyExpandedCriteria = true; | ||
}//>- | ||
this._wlQueryInfo.criteria.where = whereCriteria; | ||
return this; | ||
}, | ||
}; | ||
// ██╗ ██╗███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ██████╗ ████████╗███████╗██████╗ | ||
// ██║ ██║████╗ ██║██╔════╝██║ ██║██╔══██╗██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝██╔══██╗ | ||
// ██║ ██║██╔██╗ ██║███████╗██║ ██║██████╔╝██████╔╝██║ ██║██████╔╝ ██║ █████╗ ██║ ██║ | ||
// ██║ ██║██║╚██╗██║╚════██║██║ ██║██╔═══╝ ██╔═══╝ ██║ ██║██╔══██╗ ██║ ██╔══╝ ██║ ██║ | ||
// ╚██████╔╝██║ ╚████║███████║╚██████╔╝██║ ██║ ╚██████╔╝██║ ██║ ██║ ███████╗██████╔╝ | ||
// ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═════╝ | ||
// | ||
// ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ███████╗ | ||
// ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗██╔════╝ | ||
// ██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║███████╗ | ||
// ██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║╚════██║ | ||
// ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝███████║ | ||
// ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ | ||
// | ||
var OLD_AGGREGATION_Q_METHODS = { | ||
/** | ||
* Add the (NO LONGER SUPPORTED) `sum` clause to the criteria. | ||
@@ -638,15 +640,15 @@ * | ||
switch (category) { | ||
case 'find': _.extend(queryMethods, CRITERIA_Q_METHODS, POPULATE_Q_METHODS); break; | ||
case 'findOne': _.extend(queryMethods, CRITERIA_Q_METHODS, POPULATE_Q_METHODS); break; | ||
case 'stream': _.extend(queryMethods, CRITERIA_Q_METHODS, POPULATE_Q_METHODS, STREAM_Q_METHODS); break; | ||
case 'count': _.extend(queryMethods, CRITERIA_Q_METHODS); break; | ||
case 'sum': _.extend(queryMethods, CRITERIA_Q_METHODS); break; | ||
case 'avg': _.extend(queryMethods, CRITERIA_Q_METHODS); break; | ||
case 'find': _.extend(queryMethods, FILTER_Q_METHODS, PAGINATION_Q_METHODS, OLD_AGGREGATION_Q_METHODS, PROJECTION_Q_METHODS, POPULATE_Q_METHODS); break; | ||
case 'findOne': _.extend(queryMethods, FILTER_Q_METHODS, PROJECTION_Q_METHODS, POPULATE_Q_METHODS); break; | ||
case 'stream': _.extend(queryMethods, FILTER_Q_METHODS, PAGINATION_Q_METHODS, PROJECTION_Q_METHODS, POPULATE_Q_METHODS, STREAM_Q_METHODS); break; | ||
case 'count': _.extend(queryMethods, FILTER_Q_METHODS); break; | ||
case 'sum': _.extend(queryMethods, FILTER_Q_METHODS); break; | ||
case 'avg': _.extend(queryMethods, FILTER_Q_METHODS); break; | ||
case 'create': _.extend(queryMethods, SET_Q_METHODS); break; | ||
case 'createEach': _.extend(queryMethods, SET_Q_METHODS); break; | ||
case 'findOrCreate': _.extend(queryMethods, CRITERIA_Q_METHODS, SET_Q_METHODS); break; | ||
case 'findOrCreate': _.extend(queryMethods, FILTER_Q_METHODS, SET_Q_METHODS); break; | ||
case 'update': _.extend(queryMethods, CRITERIA_Q_METHODS, SET_Q_METHODS); break; | ||
case 'destroy': _.extend(queryMethods, CRITERIA_Q_METHODS); break; | ||
case 'update': _.extend(queryMethods, FILTER_Q_METHODS, SET_Q_METHODS); break; | ||
case 'destroy': _.extend(queryMethods, FILTER_Q_METHODS); break; | ||
case 'addToCollection': _.extend(queryMethods, COLLECTION_Q_METHODS); break; | ||
@@ -653,0 +655,0 @@ case 'removeFromCollection': _.extend(queryMethods, COLLECTION_Q_METHODS); break; |
@@ -10,3 +10,3 @@ /** | ||
var forgeStageThreeQuery = require('./forge-stage-three-query'); | ||
var transformPopulatedChildRecords = require('./private/transform-populated-child-records'); | ||
var getModel = require('../ontology/get-model'); | ||
@@ -218,3 +218,3 @@ /** | ||
var junctionTableQuery = { | ||
using: firstJoin.child,// TODO: we should use the same identity as below, right? (e.g. `firstJoin.childCollectionIdentity`) | ||
using: firstJoin.child, | ||
method: 'find', | ||
@@ -227,4 +227,3 @@ criteria: { | ||
limit: Number.MAX_SAFE_INTEGER||9007199254740991, | ||
select: [ firstJoin.childKey, secondJoin.parentKey ] | ||
// TODO: ^^verify these are column names and not attribute names | ||
select: [firstJoin.childKey, secondJoin.parentKey] | ||
}, | ||
@@ -491,3 +490,3 @@ meta: parentQuery.meta, | ||
}) (function _afterGettingPopulatedPhysicalRecords (err, populatedRecords){ | ||
}) (function _afterGettingPopulatedPhysicalRecords (err, populatedRecords){ | ||
@@ -530,8 +529,111 @@ if (err) { return done(err); } | ||
// Now, perform the transformation for each and every nested child record, if relevant. | ||
try { | ||
populatedRecords = transformPopulatedChildRecords(joins, populatedRecords, WLModel); | ||
} catch (e) { | ||
return done(new Error('Unexpected error finishing up the transformation of populated records (specifically, when transforming nested child records). ' + e.stack)); | ||
} | ||
// try { | ||
// populatedRecords = transformPopulatedChildRecords(joins, populatedRecords, WLModel); | ||
// } catch (e) { | ||
// return done(new Error('Unexpected error finishing up the transformation of populated records (specifically, when transforming nested child records). ' + e.stack)); | ||
// } | ||
// Process each record and look to see if there is anything to transform | ||
// Look at each key in the object and see if it was used in a join | ||
_.each(populatedRecords, function(record) { | ||
_.each(_.keys(record), function(key) { | ||
var attr = WLModel.schema[key]; | ||
// Skip unrecognized attributes. | ||
if (!attr) { | ||
return; | ||
}//-• | ||
// If an attribute was found in the WL schema report, and it's not a singular | ||
// or plural assoc., this means this value is for a normal, everyday attribute, | ||
// and not an association of any sort. So in that case, there is no need to | ||
// transform it. (We can just bail and skip on ahead.) | ||
if (!_.has(attr, 'foreignKey') && !_.has(attr, 'collection')) { | ||
return; | ||
}//-• | ||
// Ascertain whether this attribute refers to a populate collection, and if so, | ||
// get the identity of the child model in the join. | ||
var joinModelIdentity = (function() { | ||
// Find the joins (if any) in this query that refer to the current attribute. | ||
var joinsUsingThisAlias = _.where(joins, { alias: key }); | ||
// If there are no such joins, return `false`, signalling that we can continue to the next | ||
// key in the record (there's nothing to transform). | ||
if (joinsUsingThisAlias.length === 0) { | ||
return false; | ||
} | ||
// Get the reference identity. | ||
var referenceIdentity = attr.referenceIdentity; | ||
// If there are two joins referring to this attribute, it means a junction table is being used. | ||
// We don't want to do transformations using the junction table model, so find the join that | ||
// has the junction table as the parent, and get the child identity. | ||
if (joinsUsingThisAlias.length === 2) { | ||
return _.find(joins, { parentCollectionIdentity: referenceIdentity }).childCollectionIdentity; | ||
} | ||
// Otherwise return the identity specified by `referenceIdentity`, which should be that of the child model. | ||
else { | ||
return referenceIdentity; | ||
} | ||
})(); | ||
// If the attribute references another identity, but no joins were made in this query using | ||
// that identity (i.e. it was not populated), just leave the foreign key as it is and don't try | ||
// and do any transformation to it. | ||
if (joinModelIdentity === false) { | ||
return; | ||
} | ||
var WLChildModel = getModel(joinModelIdentity, orm); | ||
// If the value isn't an array, it must be a populated singular association | ||
// (i.e. from a foreign key). So in that case, we'll just transform the | ||
// child record and then attach it directly on the parent record. | ||
if (!_.isArray(record[key])) { | ||
if (!_.isNull(record[key]) && !_.isObject(record[key])) { | ||
throw new Error('Consistency violation: IWMIH, `record[\''+'\']` should always be either `null` (if populating failed) or a dictionary (if it worked). But instead, got: '+util.inspect(record[key], {depth: 5})+''); | ||
} | ||
record[key] = WLChildModel._transformer.unserialize(record[key]); | ||
return; | ||
}//-• | ||
// Otherwise the attribute is an array (presumably of populated child records). | ||
// (We'll transform each and every one.) | ||
var transformedChildRecords = []; | ||
_.each(record[key], function(originalChildRecord) { | ||
// Transform the child record. | ||
var transformedChildRecord; | ||
transformedChildRecord = WLChildModel._transformer.unserialize(originalChildRecord); | ||
// Finally, push the transformed child record onto our new array. | ||
transformedChildRecords.push(transformedChildRecord); | ||
});//</ each original child record > | ||
// Set the RHS of this key to either a single record or the array of transformedChildRecords | ||
// (whichever is appropriate for this association). | ||
if (_.has(attr, 'foreignKey')) { | ||
record[key] = _.first(transformedChildRecords); | ||
} else { | ||
record[key] = transformedChildRecords; | ||
} | ||
// If `undefined` is specified explicitly, use `null` instead. | ||
if (_.isUndefined(record[key])) { | ||
record[key] = null; | ||
}//>- | ||
}); // </ each key in parent record > | ||
});//</ each top-level ("parent") record > | ||
// Sanity check: | ||
@@ -538,0 +640,0 @@ // If `populatedRecords` is invalid (not an array) return early to avoid getting into trouble. |
@@ -595,8 +595,8 @@ /** | ||
// Ensure this modifier is not the empty string. | ||
if (modifier === '') { | ||
throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
'Invalid `contains` (string search) modifier. Should be provided as '+ | ||
'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
)); | ||
}//-• | ||
// if (modifier === '') { | ||
// throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
// 'Invalid `contains` (string search) modifier. Should be provided as '+ | ||
// 'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
// )); | ||
// }//-• | ||
@@ -653,8 +653,8 @@ // Convert this modifier into a `like`, making the necessary adjustments. | ||
// Ensure this modifier is not the empty string. | ||
if (modifier === '') { | ||
throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
'Invalid `startsWith` (string search) modifier. Should be provided as '+ | ||
'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
)); | ||
}//-• | ||
// if (modifier === '') { | ||
// throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
// 'Invalid `startsWith` (string search) modifier. Should be provided as '+ | ||
// 'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
// )); | ||
// }//-• | ||
@@ -711,8 +711,8 @@ // Convert this modifier into a `like`, making the necessary adjustments. | ||
// Ensure this modifier is not the empty string. | ||
if (modifier === '') { | ||
throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
'Invalid `endsWith` (string search) modifier. Should be provided as '+ | ||
'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
)); | ||
}//-• | ||
// if (modifier === '') { | ||
// throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( | ||
// 'Invalid `endsWith` (string search) modifier. Should be provided as '+ | ||
// 'a non-empty string. But the provided modifier is \'\' (empty string).' | ||
// )); | ||
// }//-• | ||
@@ -719,0 +719,0 @@ // Convert this modifier into a `like`, making the necessary adjustments. |
@@ -638,6 +638,4 @@ /** | ||
})//</self-invoking recursive function (def)> | ||
// | ||
// Kick off our recursion with the `where` clause: | ||
(whereClause, 0, undefined, undefined); | ||
})(whereClause, 0, undefined, undefined); | ||
//</self-invoking recursive function that kicked off our recursion with the `where` clause> | ||
@@ -644,0 +642,0 @@ } catch (e) { |
@@ -19,5 +19,8 @@ /** | ||
'\n'+ | ||
'> This is usually the result of a model definition changing while there is still\n'+ | ||
'> leftover data that needs to be manually updated. If that\'s the case here, then\n'+ | ||
'> to make this warning go away, just update or destroy the old records in your database.\n'+ | ||
'> You are seeing this warning because there are records in your database that don\'t\n'+ | ||
'> match up with your models. This is often the result of a model definition being\n'+ | ||
'> changed without also migrating leftover data. But it could also be because records\n'+ | ||
'> were added or modified in your database from somewhere outside of Sails/Waterline\n'+ | ||
'> (e.g. phpmyadmin or the Mongo REPL). In either case, to make this warning go away,\n'+ | ||
'> just update or destroy the old records in your database.\n'+ | ||
(process.env.NODE_ENV !== 'production' ? '> (For example, if this is stub data, then you might just use `migrate: drop`.)\n' : '')+ | ||
@@ -27,3 +30,3 @@ '> \n'+ | ||
'> believe that is the case, then please contact the maintainer of this adapter by opening\n'+ | ||
'> an issue, or visit `http://sailsjs.com/support` for help.\n', | ||
'> an issue, or visit http://sailsjs.com/support for help.\n', | ||
@@ -357,4 +360,5 @@ HARD_TO_SEE_HOW_THIS_COULD_BE_YOUR_FAULT: | ||
'in the result has a value with an unexpected data type for property `'+attrName+'`.\n'+ | ||
'The corresponding attribute declares itself an auto timestamp with `type: \''+attrDef.type+'\'`,\n'+ | ||
'but instead of a valid timestamp, the actual value in the record is:\n'+ | ||
'The model\'s `'+attrName+'` attribute declares itself an auto timestamp with\n'+ | ||
'`type: \''+attrDef.type+'\'`, but instead of a valid timestamp, the actual value\n'+ | ||
'in the record is:\n'+ | ||
'```\n'+ | ||
@@ -361,0 +365,0 @@ util.inspect(record[attrName],{depth:5})+'\n'+ |
@@ -62,7 +62,5 @@ /** | ||
Transformation.prototype.serialize = function(values, behavior) { | ||
Transformation.prototype.serializeCriteria = function(values) { | ||
var self = this; | ||
behavior = behavior || 'default'; | ||
function recursiveParse(obj) { | ||
@@ -75,26 +73,3 @@ | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: remove this: | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Handle array of types for findOrCreateEach | ||
if (_.isString(obj)) { | ||
if (_.has(self._transformations, obj)) { | ||
values = self._transformations[obj]; | ||
return; | ||
} | ||
return; | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
_.each(obj, function(propertyValue, propertyName) { | ||
// Schema must be serialized in first level only | ||
if (behavior === 'schema') { | ||
if (_.has(self._transformations, propertyName)) { | ||
obj[self._transformations[propertyName]] = propertyValue; | ||
delete obj[propertyName]; | ||
} | ||
return; | ||
} | ||
// Recursively parse `OR` or `AND` criteria objects to transform keys | ||
@@ -124,8 +99,2 @@ if (_.isArray(propertyValue) && (propertyName === 'or' || propertyName === 'and')) { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// TODO: Assuming this is still in use, then split it up into separate | ||
// utilities (consider what would happen if you named an attribute "select" | ||
// or "sort"). We shouldn't use the same logic to transform attrs to column | ||
// names in criteria as we do `newRecord` or `valuesToSet`, etc. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// If the property === SELECT check for any transformation keys | ||
@@ -155,3 +124,2 @@ if (propertyName === 'select' && _.isArray(propertyValue)) { | ||
} | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
@@ -175,4 +143,28 @@ // Check if property is a transformation key | ||
/** | ||
* Transforms a set of values into a representation used | ||
* in an adapter. | ||
* | ||
* @param {Object} values to transform | ||
* @return {Object} | ||
*/ | ||
Transformation.prototype.serializeValues = function(values) { | ||
var self = this; | ||
_.each(values, function(propertyValue, propertyName) { | ||
if (_.has(self._transformations, propertyName)) { | ||
values[self._transformations[propertyName]] = propertyValue; | ||
// Only delete if the names are different | ||
if (self._transformations[propertyName] !== propertyName) { | ||
delete values[propertyName]; | ||
} | ||
} | ||
}); | ||
return values; | ||
}; | ||
/** | ||
@@ -179,0 +171,0 @@ * .unserialize() |
{ | ||
"name": "waterline", | ||
"description": "An ORM for Node.js and the Sails framework", | ||
"version": "0.13.0-parley2", | ||
"version": "0.13.0-rc1", | ||
"homepage": "http://github.com/balderdashy/waterline", | ||
@@ -31,7 +31,7 @@ "contributors": [ | ||
"lodash.issafeinteger": "4.0.4", | ||
"parley": "^2.1.0", | ||
"parley": "^2.2.0", | ||
"rttc": "^10.0.0-1", | ||
"switchback": "2.0.1", | ||
"waterline-schema": "^1.0.0-5", | ||
"waterline-utils": "^1.3.4" | ||
"waterline-schema": "^1.0.0-7", | ||
"waterline-utils": "^1.3.7" | ||
}, | ||
@@ -57,4 +57,4 @@ "devDependencies": { | ||
"scripts": { | ||
"test": "NODE_ENV=test ./node_modules/mocha/bin/mocha test --recursive", | ||
"fasttest": "NODE_ENV=test ./node_modules/mocha/bin/mocha test --recursive", | ||
"test": "./node_modules/mocha/bin/mocha test --recursive", | ||
"fasttest": "./node_modules/mocha/bin/mocha test --recursive", | ||
"posttest": "npm run lint", | ||
@@ -61,0 +61,0 @@ "lint": "eslint lib", |
@@ -15,3 +15,3 @@ # [<img title="waterline-logo" src="http://i.imgur.com/3Xqh6Mz.png" width="610px" alt="Waterline logo"/>](http://waterlinejs.org) | ||
For detailed documentation, see [the Waterline documentation](https://github.com/balderdashy/waterline-docs). | ||
For detailed documentation, see [the Sails documentation](http://sailsjs.com). | ||
@@ -31,24 +31,4 @@ | ||
It also allows an adapter to define its own methods that don't necessarily fit into the CRUD methods defined by default in Waterline. If an adapter defines a custom method, Waterline will simply pass the function arguments down to the adapter. | ||
Waterline supports [a wide variety of adapters](http://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters) both core and community maintained. | ||
#### Community Adapters | ||
- [PostgreSQL](https://github.com/balderdashy/sails-postgresql) - *0.9+ compatible* | ||
- [MySQL](https://github.com/balderdashy/sails-mysql) - *0.9+ compatible* | ||
- [MongoDB](https://github.com/balderdashy/sails-mongo) - *0.9+ compatible* | ||
- [Memory](https://github.com/balderdashy/sails-memory) - *0.9+ compatible* | ||
- [Disk](https://github.com/balderdashy/sails-disk) - *0.9+ compatible* | ||
- [Microsoft SQL Server](https://github.com/cnect/sails-sqlserver) | ||
- [Redis](https://github.com/balderdashy/sails-redis) | ||
- [Riak](https://github.com/balderdashy/sails-riak) | ||
- [Neo4j](https://github.com/natgeo/sails-neo4j) | ||
- [OrientDB](https://github.com/appscot/sails-orientdb) | ||
- [ArangoDB](https://github.com/rosmo/sails-arangodb) | ||
- [Apache Cassandra](https://github.com/dtoubelis/sails-cassandra) | ||
- [GraphQL](https://github.com/wistityhq/waterline-graphql) | ||
- [Solr](https://github.com/sajov/sails-solr) | ||
- [Apache Derby](https://github.com/dash-/node-sails-derby) | ||
## Help | ||
@@ -94,2 +74,3 @@ Need help or have a question? Click [here](http://sailsjs.com/support). | ||
skipRecordVerification | false | Set to `true` to skip Waterline's post-query verification pass of any records returned from the adapter(s). Useful for tools like sails-hook-orm's automigrations. **Warning: Enabling this flag causes Waterline to ignore `customToJSON`!** | ||
skipExpandingDefaultSelectClause | false | Set to `true` to force Waterline to skip expanding the `select` clause in criteria when it forges stage 3 queries (i.e. the queries that get passed in to adapter methods). Normally, if a model declares `schema: true`, then the S3Q `select` clause is expanded to an array of column names, even if the S2Q had factory default `select`/`omit` clauses (which is also what it would have if no explicit `select` or `omit` clauses were included in the original S1Q.) Useful for tools like sails-hook-orm's automigrations, where you want temporary access to properties that aren\'t necessarily in the current set of attribute definitions. **Warning: Do not use this flag in your web application backend-- or at least [ask for help](https://sailsjs.com/support) first.** | ||
@@ -107,2 +88,3 @@ | ||
cascadeOnDestroy: true, | ||
fetchRecordsOnUpdate: true, | ||
@@ -118,61 +100,2 @@ fetchRecordsOnDestroy: true, | ||
## New methods | ||
Rough draft of documentation for a few new methods available in Waterline v0.13. | ||
#### replaceCollection() | ||
Replace the specified collection of one or more parent records with a new set of members. | ||
```javascript | ||
// For users 3 and 4, change their "pets" collection to contain ONLY pets 99 and 98. | ||
User.replaceCollection([3,4], 'pets') | ||
.members([99,98]) | ||
.exec(function (err) { | ||
// ... | ||
}); | ||
``` | ||
Under the covers, what this method _actually does_ varies depending on whether the association passed in uses a junction or not. | ||
> We know a plural association must use a junction if either (A) it is one-way ("via-less") or (B) it reciprocates another _plural_ association. | ||
If the association uses a junction, then any formerly-ascribed junction records are deleted, and junction records are created for the new members. Otherwise, if the association _doesn't_ use a junction, then the value of the reciprocal association in former child records is set to `null`, and the same value in newly-ascribed child records is set to the parent record's ID. (Note that, with this second category of association, there can only ever be _one_ parent record. Attempting to pass in multiple parent records will result in an error.) | ||
#### addToCollection() | ||
Add new members to the specified collection of one or more parent records. | ||
```javascript | ||
// For users 3 and 4, add pets 99 and 98 to the "pets" collection. | ||
// > (if either user record already has one of those pets in its "pets", | ||
// > then we just silently skip over it) | ||
User.addToCollection([3,4], 'pets') | ||
.members([99,98]) | ||
.exec(function(err){ | ||
// ... | ||
}); | ||
``` | ||
#### removeFromCollection() | ||
Remove members from the the specified collection of one or more parent records. | ||
```javascript | ||
// For users 3 and 4, remove pets 99 and 98 from their "pets" collection. | ||
// > (if either user record does not actually have one of those pets in its "pets", | ||
// > then we just silently skip over it) | ||
User.removeFromCollection([3,4], 'pets') | ||
.members([99,98]) | ||
.exec(function(err) { | ||
// ... | ||
}); | ||
``` | ||
## License | ||
@@ -179,0 +102,0 @@ [MIT](http://sailsjs.com/license). Copyright © 2012-2017 Mike McNeil, Balderdash Design Co., & The Sails Company |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
946305
71
14076
103
2
Updatedparley@^2.2.0
Updatedwaterline-schema@^1.0.0-7
Updatedwaterline-utils@^1.3.7