How to remove object taking into account references in Mongoose Node.js? - node.js

This is my MongoDB schema:
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}]
});
var productSchema = new mongoose.Schema({
name: String,
campaign: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Campaign'
}
]
});
var campaignSchema = new mongoose.Schema({
name: String,
});
module.exports = {
Partner: mongoose.model('Partner', partnerSchema),
Product: mongoose.model('Product', productSchema),
Campaign: mongoose.model('Campaign', campaignSchema)
}
And I wondering how can I remove object from any document taking into account references (maybe should I use somehow populate from mongoose)? For example if I will remove Product then I assume that I will remove also ref ID in Partner and all Campaigns which belong to this Product.
At the moment I removing in this way:
var campSchema = require('../model/camp-schema');
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
if (err) throw err;
res.json(response);
});
});
However in mongo still left references.

You would have to nest your calls to remove the product id from the other model. For instance, in your call to remove the product from the Product
collection, you could also make another call to remove the ref from the Partner model within the results callback. Removing the product by default will remove its refs to the Campaign Model.
The following code shows the intuition above:
var campSchema = require('../model/camp-schema');
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove({ _id: req.body.productId }, function (err, response) {
if (err) throw err;
campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } },
function (err, res){
if (err) throw err;
res.json(res);
}
);
});
});
To remove the associated campaigns then you may need an extra remove operation that takes in the associated campaign id fro a given product id. Consider the following dirty hack which may potentially award you a one-way ticket to callback hell if not careful with the callback nesting:
router.post('/removeProduct', function (req, res) {
campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
{ new: true },
function (err, product) {
if (err) throw err;
campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } },
function (err, res){
if (err) throw err;
var campaignList = product.campaign
campSchema.Campaign.remove({ "_id": { "$in": campaignList } })
.exec(function (err, res){
if (err) throw err;
res.json(product);
})
}
);
}
);
});
Although it works, the above potential pitfall can be avoided by using async/await or the async library. But firstly, to give you a better understanding of the using multiple callbacks with the async module, let's illustrate this with an example from Seven Things You Should Stop Doing with Node.js of multiple operations with callbacks to find a parent entity, then find child entities that belong to the parent:
methodA(function(a){
methodB(function(b){
methodC(function(c){
methodD(function(d){
// Final callback code
})
})
})
})
With async/await, your calls will be restructured structured as
router.post('/removeProduct', async (req, res) => {
try {
const product = await campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
{ new: true }
)
await campSchema.Partner.update(
{ "products": req.body.productId },
{ "$pull": { "products": req.body.productId } }
)
await campSchema.Campaign.remove({ "_id": { "$in": product.campaign } })
res.json(product)
} catch(err) {
throw err
}
})
With the async module, you can either use the series method to address the use of callbacks for nesting code of multiple methods which may result in Callback Hell:
Series:
async.series([
function(callback){
// code a
callback(null, 'a')
},
function(callback){
// code b
callback(null, 'b')
},
function(callback){
// code c
callback(null, 'c')
},
function(callback){
// code d
callback(null, 'd')
}],
// optional callback
function(err, results){
// results is ['a', 'b', 'c', 'd']
// final callback code
}
)
Or the waterfall:
async.waterfall([
function(callback){
// code a
callback(null, 'a', 'b')
},
function(arg1, arg2, callback){
// arg1 is equals 'a' and arg2 is 'b'
// Code c
callback(null, 'c')
},
function(arg1, callback){
// arg1 is 'c'
// code d
callback(null, 'd');
}], function (err, result) {
// result is 'd'
}
)
Now going back to your code, using the async waterfall method you could then restructure your code to
router.post('/removeProduct', function (req, res) {
async.waterfall([
function (callback) {
// code a: Remove Product
campSchema.Product.findOneAndRemove(
{ _id: req.body.productId },
function (err, product) {
if (err) callback(err);
callback(null, product);
}
);
},
function (doc, callback) {
// code b: Remove associated campaigns
var campaignList = doc.campaign;
campSchema.Campaign
.remove({ "_id": { "$in": campaignList } })
.exec(function (err, res) {
if (err) callback(err);
callback(null, doc);
}
);
},
function (doc, callback) {
// code c: Remove related partner
campSchema.Partner.update(
{ "products": doc._id },
{ "$pull": { "products": doc._id } },
function (err, res) {
if (err) callback(err);
callback(null, doc);
}
);
}
], function (err, result) {
if (err) throw err;
res.json(result); // OUTPUT OK
});
});

Related

How to update 2 collections with one request in mongoDB?

I have the following route using express and mongoose that update 2 collections from one route. The updates work and are reflected in MongoDb, however, at the end of the request the server crashes with the error: code: 'ERR_HTTP_HEADERS_SENT'. Is there a way for me to avoid this error?
router.post("/user-adopted", verify, (req, res) => {
const userId = req.body.userId;
const petId = req.body.petId;
User.findOneAndUpdate(
{ _id: userId },
{ adoptedPet: petId, petId: false, adoptionRequest: false },
function (err, result) {
if..else..
}
);
Data.findOneAndUpdate(
{ _id: petId },
{ adopted: true },
function (err, result) {
if...else..
);
});
As soon as the User record is updated it performs the operation you have defined in the callback and Data record is left out so to prevent that do the updation of Data record in the callback of user and try to use updateOne() instead of findOneAndUpdate() :
User.updateOne(
{ _id: userId },
{ adoptedPet: petId, petId: false, adoptionRequest: false },
function (err, result) {
if(err) res.send(err)
Data.updateOne(
{ _id: petId },
{ adopted: true },
function (err, result) {
if(err)
res.send(err)
else{
// Redirect where you want to go
}
}
);
}
);

How to delete Element In MongoDB property's array with MongooseJS?

I cannot remove an element inside of an array that is a property of a MongoDB Model.
Please remember this is a NodeJS module mongooseJS and not the real MongoDB so functionalities are not the same..
GOAL: Delete an object from the statusLiked array. | I have also confirmed that the value of status.id is correct.
Model:
Const userSchema = new mongoose.Schema({
myStatus: Array,
statusLiked: Array,
)};
Delete:
1. Deletes the status(works). 2. Delete the status from User.statusLiked(no work).
exports.deleteStatus = (req, res, next) => {
var CurrentPost = req.body.statusid; // sends in the status.id
Status.remove({ _id: CurrentPost }, (err) => {
if (err) { return next(err); }
// vvvv this vvv
User.update( {id: req.user.id}, { $pullAll: {_id: CurrentPost }, function(err) { console.log('error: '+err) } });
req.flash('success', { msg: 'Status deleted.' });
res.redirect('/');
});
};
What happens: The specific status(object) is deleted from the database. But the status still remains in the User.statusLiked array.
What I want to happen: Status to be deleted from the User.statusLiked array and the status to be deleted from the database. Then, reload the page and display a notification.
I got it to work somehow. Working code:
exports.deleteStatus = (req, res, next) => {
var CurrUser = req.body.userid;
var CurrentPost = req.body.post;
Status.remove({ _id: CurrentPost }, (err) => {
if (err) { return next(err); }
console.log('meeee'+CurrentPost+'user: ' +CurrUser);
req.flash('success', { msg: 'Status deleted.' });
res.redirect('/');
});
User.update(
{ _id: new ObjectId(CurrUser)},
{ $pull: { myStatus : { _id : new ObjectId(CurrentPost) } } },
{ safe: true },
function (err, obj) {
console.log(err || obj);
});
};

Callback errors in Async library

I'm using the async library together with mongoose as follows:
async.waterfall([
function(callback) {
async.map(new_tags, function(tag, callback) {
Tag.findOneAndUpdate(
{ '_id': tag._id },
{ '$setOnInsert': { '_id': tag._id, 'name': tag.name } },
{ 'upsert': true, 'new': true },
callback
);
}, callback);
}, function(tags, callback) {
for(var k = 0; k < tags.length; k++) {
res_tags.push(tags[k]._id);
}
callback(res_tags);
}
],
function(err, results) {
callback(err, results);
});
But I'm having doubts on how the catch the error at the end of async.waterfall... The code as it is will have in err, the actual resulting array (res_tags).
Can someone give me a hand?
You're not handling your callbacks appropriately. async uses error-first callbacks. This is an important concept in Node.js because this is considered the "best practice" for handling errors within a callback chain.
See this post on error-first callbacks and Node.js
See below for how to properly implement the callbacks within your code:
async.waterfall([
function(callback) {
var res
async.map(new_tags, function(tag, callback) {
Tag.findOneAndUpdate(
{ '_id': tag._id },
{ '$setOnInsert': { '_id': tag._id, 'name': tag.name } },
{ 'upsert': true, 'new': true },
function (err, doc) {
// If an error occurs, pass it back to our map callback.
if (err)
return callback(err, null);
// If there was no error return the doc
return callback(null, doc);
}
);
}, function (err, docs) {
// If an error occurred during map return it back to the waterfall
if (err)
return callback(err, null);
// Return back all docs
return callback(null, docs);
});
}, function(tags, callback) {
// For each tag push them to res_tags
async.each(tags, function(tag) {
res_tags.push(tags[k]._id);
}, function(err) {
if (err)
return callback(err, null);
return callback(null, res_tags);
});
}
],
function(err, results) {
// If an error happened during any execution in waterfall catch it and handle it
if (err)
// Error handling
else
return results; // No error, return our results
});
The first parameter of each function callback in the waterfall should be an Error object or null if there were no errors.
callback(res_tags);
Should be changed to:
callback(null, res_tags);
From the documentation (https://github.com/caolan/async#waterfall):
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});

How to implementing beforeDestroy methods in SailsJS?

I'm new using sails JS.
I have methods like this
beforeDestroy: function(borrow, next){
return Book.find({id:borrow.product})
.then(function(){
Book.update(borrow.product, {'isBorrowed' : false})
})
.then(function(){
next();
})
.catch(function(error){
next(error);
});
}
When I tried to destroy data book 'IsBorrowed' still true, how to fix this when tried t delete data, firstly find id and secondly, change data book IsBorrowed to be false? Thank Advance
Here is a solution (to your original question - just switch the isBorrowed logic around as you now need it):
book.js
module.exports = {
schema: true,
attributes: {
name: {type: 'string', required: true},
desc: {type: 'text'},
isBorrowed: {type: 'boolean', defaultsTo: false}
}
};
bookBorrowed.js
module.exports = {
schema: true,
attributes: {
book: {model: 'Book'}
},
beforeDestroy: function (criteria, next) {
var bookId = criteria.where.id;
console.log(bookId);
Book.update({id: bookId}, {isBorrowed: true})
.exec(function (err, updatedBook) {
if (err) {
next('no book..');
}
console.log('updated book', updatedBook);
next();
});
}
};
Your problem was that you should consider the relationship with id, not object.
Also, the criteria parameter passed into beforeDestroy has a where object, it isn't the model. Also, the update() function takes an object criteria, see above.
If you wish to test, replace your bootstrap.js with the following snippet:
module.exports.bootstrap = function (cb) {
var bk = {name: 'name', desc: 'desc', isBorrowed: false};
Book.create(bk).exec(function (err, book) {
if (err) {
cb(err);
}
console.log('book: ', book);
var bb = {book: book.id};
BookBorrowed.create(bb).exec(function (err, bkBorrowed) {
if (err) {
cb(err);
}
console.log('bkBorrowed: ', bkBorrowed);
BookBorrowed.destroy({id: bkBorrowed.id}).exec(function (err, bkBorrowed) {
if (err) {
cb(err);
}
cb();
});
})
});
};

Retrieve specific values from a collection in sailsjs model

Merchant.js
module.exports = {
attributes: {
name:{
type:'string',
required:true
},
drinks:{
collection:'Drinks',
via:'merchant'
}
}
};
Drinks.js
module.exports = {
attributes: {
name:{
type:'string',
required:true
},
category:{
model:'Category'
},
merchant:{
model:'Merchant'
}
}
};
Category.js
module.exports = {
attributes: {
name:{
type:'string',
required:true,
unique: true
}
}
};
I want to retrieve the merchant with the drinks associated with the given input category.
Can someone help me in the find query.
Thanks
var selectedCategory;//given input category.
async.waterfall([
function(callback) {
Drinks
.find({
category: selectedCategory
})
.exec(function(err, drinks) {
if (err) return callback(err);
callback(null, drinks);
});
},
function(drinks, callback) {
Merchant.find({
drinks: drinks //In Pairs
})
.populate('drinks')
.exec(function(err, merchants) {
if (err) return callback(err);
callback(null, merchants);
});
}
],
function(err, results) {
if (err) return res.badRequest(err);
res.ok(results);
});
$in also didn't meet my requirement. I didn't find any sails support.
So I changed my logic as in the following :
var selectedCategory = req.param("value");
async.waterfall([
function(callback) {
Category
.findOne({
name: selectedCategory
})
.exec(function(err, cat) {
if (err) return callback(err);
callback(null, cat);
});
},
function(cat,callback) {
Drinks
.find({
category: cat.id
}).populateAll()
.exec(function(err, drinks) {
if (err) return callback(err);
callback(null, drinks);
});
},
function(drinks, callback) {
var merchantIds = [];// to store unique merchant ids
drinks.forEach(function (drink) {
if (merchantIds.indexOf(drink.merchant.id) < 0) {
merchantIds.push(drink.merchant.id);
}
})
callback(null, drinks,merchantIds);
},
function(drinks,merchantIds, callback) {
Merchant
.find({ where: {'id': {"$in": merchantIds}},skip:page*perPage,limit:perPage}).populateAll()
.exec(function (err, merchantList) {
if (err) return callback(err);
callback(null, drinks,merchantList);
});
},
function(drinks,merchantList, callback) {
var merchantsList = [];
merchantList.forEach(function (merchant) {
var merchants={};//to store merchant details in JSON format
merchants['id'] = merchant.id;
merchants['name'] = merchant.name;
merchants['drinks']=[]
drinks.forEach(function (drink) {
if (merchant.id===drink.merchant.id) {
merchants['drinks'].push(drink);
}
})
merchantsList.push(merchants)
})
callback(null, merchantsList);
}
],
function(err, results) {
if (err) return res.badRequest(err);
res.json(results);
});

Resources