UNPKG

offshore

Version:
276 lines (207 loc) 7.65 kB
/** * Module Dependencies */ var async = require('async'); var _ = require('lodash'); var usageError = require('../../utils/usageError'); var utils = require('../../utils/helpers'); var normalize = require('../../utils/normalize'); var Deferred = require('../deferred'); var callbacks = require('../../utils/callbacksRunner'); var nestedOperations = require('../../utils/nestedOperations'); var hop = utils.object.hasOwnProperty; /** * Update all records matching criteria * * @param {Object} criteria * @param {Object} values * @param {Function} cb * @return Deferred object if no callback */ module.exports = function(criteria, values, cb, metaContainer) { var self = this; if (typeof criteria === 'function') { cb = criteria; criteria = null; } // Return Deferred or pass to adapter if (typeof cb !== 'function') { return new Deferred(this, this.update, criteria, values); } // If there was something defined in the criteria that would return no results, don't even // run the query and just return an empty result set. if (criteria === false) { return cb(null, []); } // Ensure proper function signature var usage = utils.capitalize(this.identity) + '.update(criteria, values, callback)'; if (!values) return usageError('No updated values specified!', usage, cb); // Format Criteria and Values var valuesObject = prepareArguments.call(this, criteria, values); // Create any of the belongsTo associations and set the foreign key values createBelongsTo.call(this, valuesObject, function(err) { if (err) return cb(err); beforeCallbacks.call(self, valuesObject.values, function(err) { if (err) return cb(err); updateRecords.call(self, valuesObject, cb, metaContainer); }); }, metaContainer); }; /** * Prepare Arguments * * @param {Object} criteria * @param {Object} values * @return {Object} */ function prepareArguments(criteria, values) { // Check if options is an integer or string and normalize criteria // to object, using the specified primary key field. criteria = normalize.expandPK(this, criteria); // Normalize criteria criteria = normalize.criteria(criteria); // Pull out any associations in the values var associations = nestedOperations.valuesParser.call(this, this.identity, this.offshore.schema, values); // Replace associated models with their foreign key values if available. // Unless the association has a custom primary key (we want to create the object) values = nestedOperations.reduceAssociations.call(this, this.identity, this.offshore.schema, values, 'update'); // Cast values to proper types (handle numbers as strings) values = this._cast.run(values); return { criteria: criteria, values: values, associations: associations }; } /** * Create BelongsTo Records * */ function createBelongsTo(valuesObject, cb, metaContainer) { var self = this; async.each(_.keys(valuesObject.associations.models), function(item, next) { // Check if value is an object. If not don't try and create it. if (!_.isPlainObject(valuesObject.values[item])) return next(); // Check for any transformations var attrName = hop(self._transformer._transformations, item) ? self._transformer._transformations[item] : item; var attribute = self._schema.schema[attrName]; var modelName; if (hop(attribute, 'collection')) modelName = attribute.collection; if (hop(attribute, 'model')) modelName = attribute.model; if (!modelName) return next(); var model = self.offshore.collections[modelName]._loadQuery(self._query); var pkValue = valuesObject.associations.models[item][model.primaryKey]; var criteria = {}; var pkField = hop(model._transformer._transformations, model.primaryKey) ? model._transformer._transformations[model.primaryKey] : model.primaryKey; criteria[pkField] = pkValue; // If a pkValue if found, do a findOrCreate and look for a record matching the pk. var query; if (pkValue) { query = model.findOrCreate(criteria, valuesObject.values[item]); } else { query = model.create(valuesObject.values[item]); } if(metaContainer) { query.meta(metaContainer); } query.exec(function(err, val) { if (err) return next(err); // attach the new model's pk value to the original value's key var pk = val[model.primaryKey]; valuesObject.values[item] = pk; // now we have pk value attached, remove it from models delete valuesObject.associations.models[item]; next(); }); }, cb); } /** * Run Before* Lifecycle Callbacks * * @param {Object} values * @param {Function} cb */ function beforeCallbacks(values, cb) { var self = this; async.series([ // Run Validation with Validation LifeCycle Callbacks function(cb) { callbacks.validate(self, values, true, cb); }, // Before Update Lifecycle Callback function(cb) { callbacks.beforeUpdate(self, values, cb); } ], cb); } /** * Update Records * * @param {Object} valuesObjecy * @param {Function} cb */ function updateRecords(valuesObject, cb, metaContainer) { var self = this; // Automatically change updatedAt (if enabled) if (this.autoUpdatedAt) { valuesObject.values.updatedAt = new Date(); } // Transform Values valuesObject.values = this._transformer.serialize(valuesObject.values); // Clean attributes valuesObject.values = this._schema.cleanValues(valuesObject.values); // Transform Search Criteria valuesObject.criteria = self._transformer.serialize(valuesObject.criteria); // Pass to adapter self.adapter._loadQuery(self._query).update(valuesObject.criteria, valuesObject.values, function(err, values) { if (err) { if (typeof err === 'object') { err.model = self._model.globalId; } return cb(err); } // If values is not an array, return an array if (!Array.isArray(values)) values = [values]; // Unserialize each value var transformedValues = values.map(function(value) { return self._transformer.unserialize(value); }); // Update any nested associations and run afterUpdate lifecycle callbacks for each parent updatedNestedAssociations.call(self, valuesObject, transformedValues, function(err) { if (err) return cb(err); async.each(transformedValues, function(record, callback) { callbacks.afterUpdate(self, record, callback); }, function(err) { if (err) return cb(err); var models = transformedValues.map(function(value) { return new self._model(value); }); cb(null, models); }); }); }, metaContainer); } /** * Update Nested Associations * * @param {Object} valuesObject * @param {Object} values * @param {Function} cb */ function updatedNestedAssociations(valuesObject, values, cb) { var self = this; var associations = valuesObject.associations || {}; // Only attempt nested updates if values are an object or an array associations.models = _.pickBy(associations.models, function(vals) { return _.isPlainObject(vals) || Array.isArray(vals); }); // If no associations were used, return callback if (_.keys(associations.collections).length === 0 && _.keys(associations.models).length === 0) { return cb(); } // Create an array of model instances for each parent var parents = values.map(function(val) { return new self._model(val); }); // Update any nested associations found in the values object var args = [parents, associations, cb]; nestedOperations.update.apply(self, args); }