How do you delete a certain id from a document in mongoose? Edit: Why didn't this work (see edit) - mongodb

So I have this kind-a-like schema at the moment
user:{ _id: string,
shifts:[_id:string],
name: ... ,
...
}
And now I want to delete a shift._id from all my users who have this.
I allready have an array of all the users their id's who have this shift._id.
I've tried this, with shift_id as the id of the shift i want to delete:
userIdArray.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});
and got the error:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:535:11)
Can somebody explain me what I did wrong?
Edit:
So what i did was, i called a function named:
function deleteShiftIdInUsers(users, shift_id){
users.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});}
and called this function in my async (req, res, next) route.
Now i just execute this code within the async function instead doing it like code...;
deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);
still new to js, so what did i do wrong?

I think, You need to call the mongoose function with async/await, you should use updateMany with $in instead of looping for user_id
var ObjectId = require("mongoose").Types.ObjectId
async function deleteShiftIdInUsers(users, shift_id){
users = users.map(user_id => ObjectId(user_id))
return await UserSchema.updateMany({"_id": {"$in": users}}, {"$pull": {"shifts": ObjectId(shift_id)} });
}
when you are calling this function
await deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);

Related

Mongoose - Modify post find result

I want to modify the result in a `schema.post('find')' middleware, but this seems impossible:
const PersonSchema = new mongoose.Schema({ name: String })
PersonSchema.post('find', function(result) {
console.log('HOOW: Post Find', result)
result.team("Real Madrid")
return result
})
const Person = mongoose.model('Person', PersonSchema)
Whenever I try to perform a find query, the console.log statement is executed but, the team field never gets added.
How can I add this field to my result?
NOTE:
My real code is a bit more complex, I need to make some queries to the database and add those results to the result of the find query.
your problem is in result.team("Real Madrid")
change to
result.team="Real Madrid"
or
result['team']="Real Madrid" (Quotes are required)
try this
PersonSchema.post('find', function(result) {
console.log('HOOW: Post Find', result)
result.team="Real Madrid"
return result
})
To add additional field to documents returned from query, you may use lean():
Person.find({...}).lean();
And because result of find() method is an array so you need to loop through it and add field to each element:
PersonSchema.post('find', function(result) {
console.log('HOOW: Post Find', result)
result.forEach(val => val.team="Real Madrid");
return result
})
You should use like this
PersonSchema.post('find', function(result) {
result= result.toObject();
console.log('HOOW: Post Find', result)
result.team("Real Madrid")
return result
})

What is the syntax for a mongoose query where I just want one property's value?

I am sending a query to mongoDB using mongoose. The collection is named Step. I want the result of this query to be an array of _id values, one per step. Currently I am getting all of the step objects in their entirety, because req.query isn't defined in this case.
service:
this.getSteps = function() {
return $http({
method: 'GET',
url: '/api/step'
})
.then(function(response) {
return response.data;
});
};
controller:
readStep: function (req, res) {
Step.find(req.query, function(err, result) {
if (err) {
res.status(500).send(err);
}
res.status(200).send(result);
});
}
Set the second parameter of the find query to '_id' to retrieve only the _id of the objects.
Step.find(req.query, '_id', function(err, result) {
This will return data like this:
[{_id: 123}, {_id: 234}]
If you want to get an array of the Step ids on their own, use the javascript map function like so
result = result.map(function(doc) {
return doc._id;
});
which will give you an array like this:
[123, 234]
You'll need to use query.select, something like as shown below:
Step.find(query).select({ "_id": 1}).then(....);
I'm not able to type much because I'm responding from my handheld.
Hope this help!

MongoDb/Mongoskin - CLEANLY Update entire document w/o specifying properties

All the examples I have seen for MongoDb & Mongoskin for update, have individual properties being updated, like so:
// this works when I specify the properties
db.collection('User').update({_id: mongoskin.helper.toObjectID(user._id)},
{'$set':{displayName:user.displayName}}, function(err, result) {
if (err) throw err;
if (result){ res.send(result)}
});
But what if I wanted the whole object/document to be updated instead:
// this does not appear to work
db.collection('User').update({_id: mongoskin.helper.toObjectID(user._id)}, {'$set':user},
function(err, result){
// do something
}
It returns the error:
// It appears Mongo does not like the _id as part of the update
MongoError: After applying the update to the document {_id: ObjectId('.....
To overcome this issue, this is what I had to do to make things work:
function (req, res) {
var userId = req.body.user._id
var user = req.body.user;
delete user._id;
db.collection('User').update({_id: mongoskin.helper.toObjectID(userId)},
{'$set':user}, function(err, result) {
if (err) throw err;
console.log('result: ' + result)
if (result){ res.send(result)}
});
})
It there a more elegant way of updating the whole document, instead of hacking it with:
delete user._id
If you want to update the whole object, you do not need a $set. I am not aware of mongoskin, but in shell you would do something like:
var userObj = {
_id: <something>
...
};
db.user.update({_id: user._id}, user);
Which I think can be translated in your mongoskin in the following way.
db.collection('User').update({_id: user._id}, user, function(){...})
But here is the problem. You can not update _id of the element in Mongo. And this is what your error tells you. So you can remove the _id from your user object and have it separately. Search by this separate _id and update with a user object without _id.

Mongoose Aggregate: Empty results never trigger the callback

I am working with mongoose and defined some aggregations in the mongoshell.
Sometimes the match criteria filters out all data. Nevertheless: Mongoose does not call a callback.
This was very easy reproducable,e.g.
Contract.aggregate( {
$match:{user:'dummydata'}},
function (err, result) {
console.log('this never happens');
});
If I put in a existing user id, the callback gets called.
If I use a non existend user id, the callback just is never called.
But how should I know what happend?
Just by accident I found out, that there is the need for a second "no results" callback.
So the right solution is:
Contract.aggregate( {
$match:{user:'dummydata'}
},
function (err, result) {
console.log('this never happens');
},
function(err,result) {
console.log('Crap there is no result');
});

Cascade style delete in Mongoose

Is there a way to delete all children of an parent in Mongoose, similar to using MySQLs foreign keys?
For example, in MySQL I'd assign a foreign key and set it to cascade on delete. Thus, if I were to delete a client, all applications and associated users would be removed as well.
From a top level:
Delete Client
Delete Sweepstakes
Delete Submissions
Sweepstakes and submissions both have a field for client_id. Submissions has a field for both sweepstakes_id, and client_id.
Right now, I'm using the following code and I feel that there has to be a better way.
Client.findById(req.params.client_id, function(err, client) {
if (err)
return next(new restify.InternalError(err));
else if (!client)
return next(new restify.ResourceNotFoundError('The resource you requested could not be found.'));
// find and remove all associated sweepstakes
Sweepstakes.find({client_id: client._id}).remove();
// find and remove all submissions
Submission.find({client_id: client._id}).remove();
client.remove();
res.send({id: req.params.client_id});
});
This is one of the primary use cases of Mongoose's 'remove' middleware.
clientSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
Sweepstakes.remove({client_id: this._id}).exec();
Submission.remove({client_id: this._id}).exec();
next();
});
This way, when you call client.remove() this middleware is automatically invoked to clean up dependencies.
In case your references are stored other way around, say, client has an array of submission_ids, then in a similar way as accepted answer you can define the following on submissionSchema:
submissionSchema.pre('remove', function(next) {
Client.update(
{ submission_ids : this._id},
{ $pull: { submission_ids: this._id } },
{ multi: true }) //if reference exists in multiple documents
.exec();
next();
});
which will remove the submission's id from the clients' reference arrays on submission.remove().
Here's an other way I found
submissionSchema.pre('remove', function(next) {
this.model('Client').remove({ submission_ids: this._id }, next);
next();
});
I noticed that all of answers here have a pre assigned to the schema and not post.
my solution would be this: (using mongoose 6+)
ClientSchema.post("remove", async function(res, next) {
await Sweepstakes.deleteMany({ client_id: this._id });
await Submission.deleteMany({ client_id: this._id });
next();
});
By definition post gets executed after the process ends pre => process => post.
Now, you're probably wondering how is this different than the other solutions provided here.
What if a server error or the id of that client was not found?
On pre, it would delete all sweeptakes and submissions before the deleting process start for client. Thus, in case of an error, it would be better to cascade delete the other documents once client or the main document gets deleted.
async and await are optional here. However, it matters on large data. so that the user wouldn't get those "going to be deleted" cascade documents data if the delete progress is still on.
At the end, I could be wrong, hopefully this helps someone in their code.
Model
const orderSchema = new mongoose.Schema({
// Множество экземпляров --> []
orderItems: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required: true
}],
...
...
});
asyncHandler (optional)
const asyncHandler = fn => (req, res, next) =>
Promise
.resolve(fn(req, res, next))
.catch(next)
module.exports = asyncHandler;
controller
const asyncHandler = require("../middleware/asyncErrHandler.middleware");
// **Models**
const Order = require('../models/order.mongo');
const OrderItem = require('../models/order-item.mongo');
// #desc Delete order
// #route DELETE /api/v1/orders/:id
// #access Private
exports.deleteOrder = asyncHandler(async (req, res, next) => {
let order = await Order.findById(req.params.id)
if (!order) return next(
res.status(404).json({ success: false, data: null })
)
await order.remove().then( items => {
// Cascade delete -OrderItem-
items.orderItems.forEach( el => OrderItem.findById(el).remove().exec())
}).catch(e => { res.status(400).json({ success: false, data: e }) });
res.status(201).json({ success: true, data: null });
});
https://mongoosejs.com/docs/api/model.html#model_Model-remove