SailsJS reverse destruction of created models fails - sails.js

Suppose I have 2 models (one-to-many); Center model (one) - Room model (many).
When creating a center, an array of rooms is created in the Center.Create callback.
If a room creation fails, it should destroy all the created data before the failed room entity.
CenterController create:
create: function(req, res) {
console.log('params: ', req.params.all());
var centerObj = {
name: req.param('center_name'),
state: req.param('center_state')
};
var roomsInput = req.params('rooms');
console.log('created center centerObj: ', centerObj);
Center.create(centerObj, function centerCreated(err, center) {
if (err) {
console.log(err);
req.session.flash = {
err: err
}
console.log("Error in center create")
return res.redirect('/center/new');
}
// keep track of successfully created rooms
var created_rooms_ids = new Array();
async.mapSeries(
// array to iterate over
roomsInput,
// iterator function
function(roomInput, cb)
{
var roomObj = {
name: roomInput.name,
center: center.id,
min_age: roomInput.min_age,
max_age: roomInput.max_age
};
Room.create(roomObj, function roomCreated(err, room) {
if (err) {
console.log("Room.create error: ", err);
return cb(err);
}
created_rooms_ids.push(room.id);
return cb(null, room.id);
});
},
// callback for when the loop is finished
function(err, results)
{
if (err) {
console.log('error');
return destroyCreatedResources(err);
}
console.log('center: ', center);
return res.redirect('/center/show/' + center.id);
// destroy created resources (center + room)
function destroyCreatedResources(err)
{
console.log("destroyCreatedResources. Center=", center.id, "Id=", created_rooms_ids);
Center.destroy({id: center.id}).exec(function(e){
Room.destroy({id: created_rooms_ids}).exec(function(e){
console.log('Room.destroy');
if (e) {console.log('Room.destroy error!!!!!!!!!!!!!!!!!!!!!!');}
return res.serverError(err);
});
});
}
}
);
});
},
Problem
When an error happens in the middle and I want to perform reverse destruction of all the created rooms, only the center is destroyed.
How come res.serverError(err); is called before the Rooms are destroyed?
function destroyCreatedResources(err)
{
Center.destroy({id: center.id}).exec(function(e){
console.log('Room.destroy');
if (e) {console.log('Room.destroy error!!!!!!!!!!!!!!!!!!!!!!');}
Room.destroy({id: created_rooms_ids}).exec(function(e){
return res.serverError(err);
});
});
}
Are there better ways to do reverse destruction?

Looks like this was due to a bug in sails-mongo v0.10.0-rc2. This has now been patched and released as v0.10.0-rc3, so you can pull down the latest from npm and the issue should be resolved. Thanks!

Related

Waiting for meteor cursor in method

I have a large aggrogate query that required me to pass "allowDiskUse: true" as an option. This would not work with the aggegate as described here:
https://github.com/meteorhacks/meteor-aggregate/issues/11
My meteor method is defined here. When I call the method I need to wait for ondata to complete before anything is returned to the client, but nothing I try allows me to get that data in a safe way up to the front end.
Meteor.methods({
'getSummary': function (dept,startDate,endDate,filterType) {
f = myQuery(startdate,enddate,dayFinalGroup);
f.on("data", Meteor.bindEnvironment(function(row) {
//load an array or something here to return
}));
f.once("end", Meteor.bindEnvironment(function() {
// tidy up, in my case end the stream
}));
//here I'd return the array loaded
},
});
This is my front end.
Meteor.call(
'getSummary',0,Session.get('start_date'),Session.get('end_date'),1,
function(error, result){
if(error){
console.log(error);
} else {
Session.set('sumTotals',result);
}
}
);
Finally Got it. I utilized wrapSync
'getSummary': function (dept,startDate,endDate,filterType) {
console.log(dept);
console.log(startDate);
console.log(endDate);
console.log(filterType);
var startdate = new Date(startDate);
var enddate = new Date(endDate);
var arr = [];
f = myQuery(startdate,enddate,dayFinalGroup);
var fetchCursor = Meteor.wrapAsync(function fetchCursor (cursor, cb) {
cursor.each(function (err, doc) {
if (err) return cb(err);
if (!doc) return cb(null, { done: true }); // no more documents
arr.push(doc);
});
});
var myData = fetchCursor(f);
return arr;

Multiple functions in restify function to elasticsearch client

I'm building a REST API using node and restify that communicaties with an elasticsearch database. Now when I delete an object, I want this to do a kind of cascading delete to some other objects. I know this is not really what to use elasticsearch for but bear with me.
So here is my code:
function deleteHostname(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
var endpoints = [];
client.search({
index: 'test',
type: 'something',
body: {
from: 0, size: 100,
query: {
match: {
hostname: 'www.test.com'
}
}
}
}).then(function (error, resp) {
if(error) {
res.send(error);
}
endpoints = resp.hits.hits;
for (index = 0, len = endpoints.length; index < len; ++index) {
client.delete({
index: 'test',
type: 'something',
id: endpoints[index]._id
}, function (error, response) {
if(error) {
res.send(error);
}
});
}
res.send(endpoints);
return next();
});
}
So basically I just want to search for any objects with hostname www.test.com ( I just hard coded this to test it ). Then I want to delete all objects I found. It follows the error path and sends me this:
{
"took":1,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"failed":0
},
"hits":{
"total":1,
"max_score":2.098612,
"hits":[
{
"_index":"test",
"_type":"something",
"_id":"123456",
"_score":2.098612,
"_source":{
"duration":107182,
"date":"2016-05-04 00:54:43",
"isExceptional":true,
"hostname":"www.test.com",
"eta":613,
"hasWarnings":false,
"grade":"A+",
"ipAddress":"ipip",
"progress":100,
"delegation":2,
"statusMessage":"Ready"
}
}
]
}
}
So in my opinion this doesn't look like an error? So why am I getting it back as an error? If I remove:
if(error) {
res.send(error);
}
From my code, I won't get any response.
You need to change your code like this (see the changes denoted by -> to the left):
if(error) {
1-> return res.send(error);
}
endpoints = resp.hits.hits;
for (index = 0, len = endpoints.length; index < len; ++index) {
2-> (function(id){
client.delete({
index: 'test',
type: 'something',
3-> id: id
}, function (error, response) {
if(error) {
4-> next(error);
}
});
5-> })(endpoints[index._id]);
}
6-> //res.send(endpoints);
I'm now explaining each change:
If you don't return you'll send the error and then you'll continue with processing the hits
(3/5) Since client.delete is an asynchronous function, you need to call it in an anonymous function
In case of error you need to call next(error) not res.send
You cannot send the response at this point since your for loop might not be terminated yet. Instead of a for loop, you should use the excellent async library instead (see an example of using asynch.each below)
Async example:
var async = require('async');
...
if(error) {
return res.send(error);
}
endpoints = resp.hits.hits;
async.each(endpoints,
function(endpoint, callback) {
client.delete({
index: 'test',
type: 'something',
id: endpoint._id
}, callback);
},
// this is called when all deletes are done
function(err){
if (err) {
next(err);
} else {
res.send(endpoints);
next();
}
}
);
Another solution for you to achieve exactly what you want is to use the delete by query plugin. That feature allows you to do all the above in a single query.
If you are still on ES 1.x, delete-by-query is still part of the core and you can simply call the deleteByQuery function of the Javascript client.
If you are on ES 2.x, delete-by-query is now a plugin, so yo need to install it and then also require the deleteByQuery extension library for the Javascript client
function deleteHostname(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
client.deleteByQuery({
index: 'test',
type: 'something',
body: {
query: {
match: { hostname: 'www.test.com' }
}
}
}, function (error, response) {
if (error) {
next(error);
} else {
res.send(endpoints);
next();
}
});
}

Using design documents in pouchDB with crypto-pouch

After testing pouchDB for my Ionic project, I tried to encrypt my data with crypto-pouch. But I have a problem with using design documents. I used the following code:
One of my design documents:
var allTypeOne = {
_id: '_design/all_TypeOne',
views: {
'alle_TypeOne': {
map: function (doc) {
if (doc.type === 'type_one') {
emit(doc._id);
}
}.toString()
}
}
};
For init my database:
function initDB() {
_db = new PouchDB('myDatabase', {adapter: 'websql'});
if (!_db.adapter) {
_db = new PouchDB('myDatabase');
}
return _db.crypto(password)
.then(function(){
return _db;
});
// add a design document
_db.put(allTypeOne).then(function (info) {
}).catch(function (err) {
}
}
To get all documents of type_one:
function getAllData {
if (!_data) {
return $q.when(_db.query('all_TypeOne', { include_docs: true}))
.then(function(docs) {
_data = docs.rows.map(function(row) {
return row.doc;
});
_db.changes({ live: true, since: 'now', include_docs: true})
.on('change', onDatabaseChange);
return _data;
});
} else {
return $q.when(_data);
}
}
This code works without using crypto-pouch well, but if I insert the _db.crypto(...) no data is shown in my list. Can anyone help me? Thanks in advance!
I'm guessing that your put is happening before the call to crypto has finished. Remember, javascript is asynchronous. So wait for the crypto call to finish before putting your design doc. And then use a callback to access your database after it's all finished. Something like the following:
function initDB(options) {
_db = new PouchDB('myDatabase', {adapter: 'websql'});
if (!_db.adapter) {
_db = new PouchDB('myDatabase');
}
_db.crypto(password)
.then(function(){
// add a design document
_db.put(allTypeOne).then(function (info) {
options.success(_db);
})
.catch(function (err) { console.error(err); options.error(err)})
.catch(function (err) { console.error(err); options.error(err);})
}
}
initDB({
success:function(db){
db.query....
}
)

Sails inconsistent record creation

I use the following piece of code to create some records. If I provide incorrect values, say(password and passwordConfirmation does not match), then sometimes an institute record is created without a rollback and sometimes, rollback happens properly.
I would appreciate any help. Is there a better way to do this?
create: function (req, res) {
User.query("BEGIN TRANSACTION", function(result){
if(result) {
sails.log.info(result);
return res.serverError(result);
} else {
Institute.create({
name: req.param('name'),
shortName: req.param('shortName'),
phoneNumber: req.param('phoneNumber'),
subdomain: req.param('subdomain'),
managerEmail: req.param('email')
}, function(error, institute){
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.create({
email: req.param('email'),
password: req.param('password'),
passwordConfirmation: req.param('passwordConfirmation'),
account: institute.id
}, function(error, user) {
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.query("COMMIT", function(result){
sails.log.info(result);
return res.created(user);
});
}
});
}
});
}
});
}
You have a few of options, in no particular order.
1. Write a function that makes all the possible security checks before creation occurs, or use the beforeCreate life cycle call for your models.
For example, you could write a function verifyParams(params) that makes checks such as password comparison (and any other checks you want) for your user creation parameters before you create the institution, or you could just include these checks in your institution creation's beforeCreate method.
2. Delete if there is an error during your user creation
Delete theInstitute model instance in your error case of user creation:
...
User.create(..., function (error, user) {
if (error) {
Institute.destroy(institute.id, function instDestroyed(err) {
...
});
} else {
...
}
});
3. Create a user in your institute model's beforeCreate method.
module.exports = {
attributes: { ... },
beforeCreate: function(values, next) {
User.create(..., function (err, user) {
if (err) { return next(err) }
return next();
});
}
}
Personally, I use method #2 in my own apps.

SailsJS how to write non-async Model.CRUD functions?

I've a Parent and Child models in my app.
Parent.create receives parent_name and an array of children that I want to add to the Parent model, the following flow describes the function:
1) Create parent object
2) Create all children
3) Save parent with updated children array
The problem is that Parent.create is probably async, and the 'created_children' array when saved to parent is empty (because it doesn't wait until the Parent.create finishes.
How can I make Model.create dependent (or synchronic)?
See the code below (I commented the buggy part //BUG: EMPTY ARRAY!!!!!!!!!!):
create: function(req, res, next) {
var childrenInput = req.param('children');
var parentObj = {
name: req.param('parent_name')
};
Parent.create(parentObj, function parentCreated(err, parent) {
if (err) {
return res.redirect('/parent/new');
}
// assign children
var created_children = new Array();
for(var i=0; i < childrenInput.length; i++) {
var childObj = {
name: parentObj.childrenInput[i],
parent_id: parent.id
};
// create child
Child.create(childObj, function childCreated(err, child) {
if (err) {
for(var j=0; j < created_children.length; j++) {
Child.destroy(created_children[j].id, function childDestroyed(err) {
if (err)
{
// BIG ERROR
return next(err);
}
});
}
return res.redirect('/parent/new');
}
// add created child
created_children.push(child.id);
}) // end of Child.create;
} // end of for;
// save created children to parent
parent.children = created_children.slice();
parent.save(function(err, c) {
if (err)
{
// TODO: FUNCTION TO DESTROY ALL CHILDREN
return next(err);
}
});
return res.redirect('/parent/show/' + parent.id);
});
},
Parent model
module.exports = {
schema: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
children: {
type: 'array',
defaultsTo: []
}
}
};
Performing asynchronous operations on an array can be a real pain. I'd suggest using a module like async, which provides synchronous-like functionality for asynchronous code. You could then rewrite your code as:
Parent.create(parentObj, function parentCreated(err, parent) {
if (err) {
return res.redirect('/parent/new');
}
// You only really need this for error handling...
var created_children_ids = new Array();
// Create an array of child instances from the array of child data
async.map(
// Array to iterate over
childrenInput,
// Iterator function
function(childObj, callback) {
Child.create(childObj, function childCreated(err, child) {
if (err) {return callback(err);}
created_children_ids.push(child.id);
// 'null' indicates no error
return callback(null, child);
});
},
// Callback for when loop is finished.
// If any run of the iterator function resulted in the
// callback being called with an error, it will immediately
// exit the loop and call this function. Otherwise the function
// is called when the loop is finished, and "results" contains
// the result of the mapping operation
function (err, results) {
if (err) {return destroyChildren();}
// Save the children to the parent
parent.children = results;
parent.save(function(err, c) {
if (err) {return destroyChildren();}
return res.redirect('/parent/show/' + parent.id);
});
function destroyChildren(err) {
Child.destroy({id: created_children_ids}).exec(function() {
// Respond with an error
return res.serverError(err);
});
}
}
);
});
Note that if you're using Sails v0.10, you can use actual associations to bind the parent and child records, and use parent.children.add(childObj) (which is a synchronous operation) in a regular loop prior to calling parent.save(). Calling .add with an object will cause that model to be created during the save operation.