UNPKG

water-orm

Version:

A monolith version of Standalone waterline ORM

258 lines (193 loc) 7.14 kB
/** * Module Dependencies */ var async = require('async'); var _ = require('lodash'); var utils = require('../../utils/helpers'); var Deferred = require('../deferred'); var callbacks = require('../../utils/callbacksRunner'); var nestedOperations = require('../../utils/nestedOperations'); var hop = utils.object.hasOwnProperty; /** * Create a new record * * @param {Object || Array} values for single model or array of multiple values * @param {Function} callback * @return Deferred object if no callback */ module.exports = function(values, cb, metaContainer) { var self = this; // Handle Deferred where it passes criteria first if(_.isPlainObject(arguments[0]) && (_.isPlainObject(arguments[1]) || _.isArray(arguments[1]))) { values = arguments[1]; cb = arguments[2]; } // Loop through values and pull out any buffers before cloning var bufferValues = {}; _.each(_.keys(values), function(key) { if (Buffer.isBuffer(values[key])) { bufferValues[key] = values[key]; } }); values = _.cloneDeep(values) || {}; // Replace clone keys with the buffer values _.each(_.keys(bufferValues), function(key) { values[key] = bufferValues[key]; }); // Remove all undefined values if (_.isArray(values)) { values = _.remove(values, undefined); } // Return Deferred or pass to adapter if (typeof cb !== 'function') { return new Deferred(this, this.create, {}, values); } // Handle Array of values if (Array.isArray(values)) { return this.createEach(values, cb, metaContainer); } // Process Values var valuesObject = processValues.call(this, 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, function(err) { if (err) return cb(err); createValues.call(self, valuesObject, cb, metaContainer); }, metaContainer); }); }; /** * Process Values * * @param {Object} values * @return {Object} */ function processValues(values) { // Set Default Values if available for (var key in this.attributes) { if ((!hop(values, key) || values[key] === undefined) && hop(this.attributes[key], 'defaultsTo')) { var defaultsTo = this.attributes[key].defaultsTo; values[key] = typeof defaultsTo === 'function' ? defaultsTo.call(values) : _.clone(defaultsTo); } } // Pull out any associations in the values var _values = _.cloneDeep(values); var associations = nestedOperations.valuesParser.call(this, this.identity, this.waterline.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.waterline.schema, values, 'create'); // Cast values to proper types (handle numbers as strings) values = this._cast.run(values); return { values: values, originalValues: _values, associations: associations }; } /** * Create BelongsTo Records * */ function createBelongsTo(valuesObject, cb, metaContainer) { var self = this; async.each(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.waterline.collections[modelName]; var pkValue = valuesObject.originalValues[item][model.primaryKey]; var criteria = {}; criteria[model.primaryKey] = 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; next(); }); }, cb); } /** * Run Before* Lifecycle Callbacks * * @param {Object} valuesObject * @param {Function} cb */ function beforeCallbacks(valuesObject, cb) { var self = this; async.series([ // Run Validation with Validation LifeCycle Callbacks function(cb) { callbacks.validate(self, valuesObject.values, false, cb); }, // Before Create Lifecycle Callback function(cb) { callbacks.beforeCreate(self, valuesObject.values, cb); } ], cb); } /** * Create Parent Record and any associated values * * @param {Object} valuesObject * @param {Function} cb */ function createValues(valuesObject, cb, metaContainer) { var self = this; var date; // Automatically add updatedAt and createdAt (if enabled) if (self.autoCreatedAt) { if (!valuesObject.values[self.autoCreatedAt]) { date = date || new Date(); valuesObject.values[self.autoCreatedAt] = date; } } if (self.autoUpdatedAt) { if (!valuesObject.values[self.autoUpdatedAt]) { date = date || new Date(); valuesObject.values[self.autoUpdatedAt] = date; } } // Transform Values valuesObject.values = self._transformer.serialize(valuesObject.values); // Clean attributes valuesObject.values = self._schema.cleanValues(valuesObject.values); // Pass to adapter here self.adapter.create(valuesObject.values, function(err, values) { if (err) { if (typeof err === 'object') { err.model = self._model.globalId; } return cb(err); } // Unserialize values values = self._transformer.unserialize(values); // If no associations were used, run after if (valuesObject.associations.collections.length === 0) return after(values); var parentModel = new self._model(values); nestedOperations.create.call(self, parentModel, valuesObject.originalValues, valuesObject.associations.collections, function(err) { if (err) return cb(err); return after(parentModel.toObject()); }); function after(values) { // Run After Create Callbacks callbacks.afterCreate(self, values, function(err) { if (err) return cb(err); // Return an instance of Model var model = new self._model(values); cb(null, model); }); } }, metaContainer); }