sails
Version:
API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)
145 lines (119 loc) • 5.43 kB
JavaScript
/**
* Module dependencies
*/
var _ = require('@sailshq/lodash');
var async = require('async');
var formatUsageError = require('../formatUsageError');
/**
* Create Record
*
* http://sailsjs.com/docs/reference/blueprint-api/create
*
* An API call to crete a single model instance using the specified attribute values.
*
*/
module.exports = function createRecord (req, res) {
var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;
// Set the blueprint action for parseBlueprintOptions.
req.options.blueprintAction = 'create';
var queryOptions = parseBlueprintOptions(req);
var Model = req._sails.models[queryOptions.using];
// Get the new record data.
var data = queryOptions.newRecord;
// Look for any many-to-one collections that are being set.
// For example, User.create({pets: [1, 2, 3]}) where `pets` is a collection of `Pet`
// via an `owner` attribute that is `model: 'user'`.
// We need to know about these so that, if any of the new children already had parents,
// those parents get `removedFrom` notifications.
async.reduce(_.keys(Model.attributes), [], function(memo, attrName, nextAttrName) {
var attrDef = Model.attributes[attrName];
if (
// Does this attribute represent a plural association.
attrDef.collection &&
// Is this attribute set with a non-empty array?
_.isArray(data[attrName]) && data[attrName].length > 0 &&
// Does this plural association have an inverse attribute on the related model?
attrDef.via &&
// Is that inverse attribute a singular association, making this a many-to-one relationship?
req._sails.models[attrDef.collection].attributes[attrDef.via].model
) {
// Create an `in` query looking for all child records whose primary keys match
// those in the array that the new parent's association attribute (e.g. `pets`) is set to.
var criteria = {};
criteria[req._sails.models[attrDef.collection].primaryKey] = data[attrName];
req._sails.models[attrDef.collection].find(criteria).exec(function(err, newChildren) {
if (err) {return nextAttrName(err);}
// For each child, see if the inverse attribute already has a value, and if so,
// push a new `removedFrom` notification onto the list of those to send.
_.each(newChildren, function(child) {
if (child[attrDef.via]) {
memo.push({
id: child[attrDef.via],
removedId: child[req._sails.models[attrDef.collection].primaryKey],
attribute: attrName
});
}
});
return nextAttrName(undefined, memo);
});
}
else {
return nextAttrName(undefined, memo);
}
}, function (err, removedFromNotificationsToSend) {
if (err) {return res.serverError(err);}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: Use a database transaction here, if supported by the datastore.
// e.g.
// ```
// Model.getDatastore().transaction(function during(db, proceed){ ... })
// .exec(function afterwards(err, result){}));
// ```
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Create new instance of model using data from params
Model.create(data).meta(queryOptions.meta).exec(function created (err, newInstance) {
// Differentiate between waterline-originated validation errors
// and serious underlying issues. Respond with badRequest if a
// validation error is encountered, w/ validation info, or if a
// uniqueness constraint is violated.
if (err) {
switch (err.name) {
case 'AdapterError':
switch (err.code) {
case 'E_UNIQUE': return res.badRequest(err);
default: return res.serverError(err);
}//•
case 'UsageError': return res.badRequest(formatUsageError(err, req));
default: return res.serverError(err);
}
}//-•
// If we didn't fetch the new instance, just return 'OK'.
if (!newInstance) {
return res.ok();
}
// Look up and populate the new record (according to `populate` options in request / config)
Model
.findOne(newInstance[Model.primaryKey], queryOptions.populates)
.exec(function foundAgain(err, populatedRecord) {
if (err) { return res.serverError(err); }
if (!populatedRecord) { return res.serverError('Could not find record after creating!'); }
// If we have the pubsub hook, use the model class's publish method
// to notify all subscribers about the created item
if (req._sails.hooks.pubsub) {
if (req.isSocket) {
Model.subscribe(req, [populatedRecord[Model.primaryKey]]);
Model._introduce(populatedRecord);
}
Model._publishCreate(populatedRecord, !req.options.mirror && req);
if (removedFromNotificationsToSend.length) {
_.each(removedFromNotificationsToSend, function(notification) {
Model._publishRemove(notification.id, notification.attribute, notification.removedId, !req.options.mirror && req, {noReverse: true});
});
}
}//>-
// Send response
res.ok(populatedRecord);
}); // </foundAgain>
});
});
};