Updating a nested document mongo. Concurrency issues - node.js

I am struggling with a seemingly basic issue in Mongoose.
I have a method that looks like so:
changeStateOnIncident: function(req, res, next) {
Incident.findOneQ({ _id: req.params.id })
.then(function(incident) {
if (!incident) { return next(new restify.ResourceNotFoundError("Incident with id " + req.params.id + " not found.")) }
return incident.addStateChange(req.body)
.then(function(savedState) {
return res.json(201, savedState);
})
.then(function() {
var alertOrders = _.map(incident.alertees, function(alertee) {
return alertee.createAndSendAlert(req.body)
})
return Q.allSettled(alertOrders)
.then(function(results) {
return incident.save();
})
})
})
.fail(function(err) {
return next(new restify.InvalidContentError(JSON.stringify(err)))
})
},
As for createAndSendAlert and addStateChange, their definitions are here
What I am trying to do here is as follows:
Find the Incident matching the ID passed in.
Add the state event to the array of state changes.
Return that newly added change event back to the user.
For every alertee on the Incident, loop through and add an alert to that alertees list of alerts, the result of an SMS request.
Save that incident.
Unfortunately, the way I'm doing it now is failing for concurrent writes. Suppose I send two
{
_id: "53cc87d0feeb4302007eadbe",
alertees: [
{
_id: "eJ65nfJh_g",
alerts: [
{
"_id": "53cc87d1feeb4302007eadc1",
"created_at": "2014-07-21T03:24:01.304Z",
"response": "Sent",
"state": "First state change",
"updated_at": "2014-07-21T03:24:06.802Z"
},
{
"_id": "53cc87d2feeb4302007eadc5",
"created_at": "2014-07-21T03:24:02.348Z",
"response": "Sent",
"state": "Emergency",
"updated_at": "2014-07-21T03:24:06.802Z"
},
{
"_id": "53cc87d6feeb4302007eadcc",
"response": "Sent"
"state": "Ended",
}
]
}
],
states: [
{
_id: "53cc87d1feeb4302007eadbf",
state: "First state change"
},
{
_id: "53cc87d1feeb4302007eadc2",
state: "Second state change"
},
{
_id: "53cc87d2feeb4302007eadc4",
state: "Third state change"
},
{
_id: "53cc87d6feeb4302007eadcb",
state: "Fourth state change"
}
]
}
I think what is happening is IF I send two states A and B. And A's SMS response comes back before B's it will possibly invalidate the document B is trying to modify. I am losing changes to the sub document if state changes are sent too close to each other.
Any idea? How could I rewrite this? Using update methods perhaps?

Related

Remove object from nested array in MongoDB using NodeJS

I can see that this question should have been answered here, but the code simply doesn't work for me (I have tried multiple, similar variations).
Here is my data:
[{
"_id": {
"$oid": "628cadf43a2fd997be8ce242"
},
"dcm": 2,
"status": true,
"comments": [
{
"id": 289733,
"dcm": 2,
"status": true,
"clock": "158",
"user": "Nathan Field",
"dept": "IT",
"department": [],
"dueback": "",
"comment": "test 1"
},
{
"id": 289733,
"dcm": 2,
"status": true,
"clock": "158",
"user": "Nathan Field",
"dept": "IT",
"department": [],
"dueback": "",
"comment": "test 2"
}
],
"department": [],
"dueback": ""
}]
And here is my code
const deleteResult = await db.collection('status').updateOne(
{ "dcm": comments.dcm },
{ $pull: { "comments": { "id": comments.id } } },
{ upsert: false },
{ multi: true }
);
Absolutely nothing happens...
So the issue ended up being something to do with running multiple update operations within one function. I have a database connection function like this:
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('collection');
await operations(db);
client.close();
} catch (error) {
res.status(500).json({ message: 'Error connecting to db', error });
}
}
And then I call this by using:
withDB(async (db) => {
await db.collection('status').updateMany(
{ "dcm": comments.dcm },
{ $pull: { "comments": { "id": comments.id } } },
{ multi: true }
);
});
The issue occurred it would seem because I had two of these update operations within one withDB function. I have multiple operations in other instances (update item, then fetch collection), but for some reason this caused an issue.
I created a separate call to the withDB function to perform the '$pull' (delete) request, and then updated the array with the new comments.
To check that there was nothing wrong with my actual query, I used Studio3T's IntelliShell feature. If I'd done that sooner I would saved myself a lot of time!

Loopback + mongoDB: Can't find extended user

I am using Loopback 3.0 with MongoDB connector.
In a REST method exposed somewhere, I need to access the currently logged user and make some updates on it.
I have extended the base User model, calling it appUser, the login works, and I can get the token (after I changed the token model to point to the appUser) of a logged user. The model is the following one:
{
"name": "appUser",
"plural": "appUsers",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"gender": {
"type": "string",
"enum": [
"M",
"F"
]
},
"birthDate": {
"type": "Date"
}
},
"validations": [],
"relations": {},
"acls": []
}
I need to access the user profile, in order to update it. But when I query it, I get null as result.
const User = app.models.appUser;
User.findOne({
where: {
_id: ObjectId("5aae7ecd2ed1b11b1c09cf25")
}
}, (err, user) => {
if (err || res == null) {
console.log("Error updating the user ");
const error = {
"name": "Database error",
"status": 500,
"message": "Can't access the database."
}
callback(error, null, null);
} else {
//Whatever
}
});
But if I run the same query from Robo3T on MongoDB, it works.
db.getCollection('appUser').find({"_id": ObjectId("5aae7ecd2ed1b11b1c09cf25")})
What am I doing wrong?
Thank you,
Massimo
You didn't call the user so that should be the case, also in your callback you are passing null and not your user result. However I don't get where the res variable came from.
const User = app.models.appUser;
User.findOne({
where: {
_id: ObjectId("5aae7ecd2ed1b11b1c09cf25"),
}
}, (err, user) => {
if (err) {
console.log("Error updating the user", err); // logs the app error
const error = {
"name": "Database error",
"status": 500,
"message": "Can't access the database."
}
callback(error, null); // passes your custom error
}
console.log("APP USER", user);
callback(null, user);
});
I don't how you calling your callback but i think you can manage this.
If you still have no result try changing _id to id

Mongoose: Update does not work in nested array object

I have a document with the array of objects and one object contains multiple objects I want to update inner object with $set but didn't get any luck.
can anybody give me any hint so that I can resolve it?.
This is my object:
{
"_id": ObjectId("56fbfafdf86fa6161911d104"),
"site": "xyz",
"adsPerCategory": NumberInt(2),
"sampledAt": ISODate("2016-03-30T16:12:45.138+0000"),
"items": [
{
"id": "4563873",
"content": {
"title": "WATER DISTILLERS",
"body": "Perfect to save money.",
}
},
{
"id": "4563s23232873",
"content": {
"title": "Cola water",
"body": "Perfect for body.",
}
}
]
}
I want to update body.
for now, I have given single object but it can be multiple.
Here what I tried
models.Sample.update(
{
_id: samples._id
},
'$set': {
'items.0.content.body': body.description
},
function(err, numAffected) {
console.log(err);
console.log('Affected....', numAffected);
}
);
It's working fine if I put 0 but I want to make it dynamic.
Like 'items.index.content.body': body.description
Thank You.
I think you can do something like this.
models.Sample.find({ _id: ObjectId(samples._id) })
.forEach(function (doc) {
doc.items.forEach(function (element, index, array) {
items[index].content.body = body.description;
});
models.Sample.save(doc);
});

Cascade delete from array using Mongoose middleware remove hook

I am building a Node.js express RESTfull API using Mongodb and mongoose.
This is my schema:
var UserSchema = new mongo.Schema({
username: { type: String },
password: { type: String, min: 8 },
display_name: { type: String, min: 1 },
friends: { type: [String] }
});
UserSchema.post('remove', function(next){
console.log({ friends: this._id }); // to test if this gets reached (it does)
UserSchema.remove({ friends: this._id });
});
And this is the function that removes a User:
.delete(function(req, res) {
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.status(500);
res.send(err);
} else {
if (user != null) {
user.remove();
res.json({ message: 'User successfully deleted' });
} else {
res.status(403);
res.json({ message: 'Could not find user.' });
res.send();
}
}
});
});
What I need to do is when a user is removed, his or her _id (String) should also be removed from all the other users' friends array. Hence the remove hook in the schema.
Right now the user gets deleted and the hook gets triggered, but the user _id is not removed from the friends array (tested with Postman):
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
},
{
"_id": "5633d1c02a8cd82f5c7c55d4",
"display_name": "Johnybruh",
"password": "donttouchjohnsstuff",
"username": "John stuff n things",
"__v": 0,
"friends": []
}
]
To this:
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
}
]
To try and figure it out I have looked at the Mongoosejs Documentation, but the mongoose doc example doesn't cover the remove hook. Also this stackoverflow qestion but this question seems to be about removing from other schemas.
I think i'm doing the remove in the hook wrong, but I can't seem to find the problem.
Thanks in advance!
EDIT:
I could not get the first suggestion by cmlndz to work, so I ended up fetching all the documents with arrays that contained the to-be-deleted users' id and pulling it from them one-by-one:
The delete function now contains this bit of code that does the magic:
// retrieve all documents that have this users' id in their friends lists
User.find({ friends: user._id }, function(err, friends) {
if (err) {
res.json({ warning: 'References not removed' });
} else {
// pull each reference to the deleted user one-by-one
friends.forEach(function(friend){
friend.friends.pull(user._id);
friend.save(function(err) {
if (err) {
res.json({ warning: 'Not all references removed' });
}
});
});
}
});
You could use $pull to find all documents that contain the "ID" in the "friends" array -or- find any matching document and popping the "ID" out of the array one by one.

create a new object Id in mongoDB using node js

I am using the below code to insert data to mongodb
router.post('/NewStory', function (req, res) {
var currentObject = { user: userId , story : story , _id:new ObjectID().toHexString() };
req.db.get('clnTemple').findAndModify({
query: { _id: req.body.postId },
update: { $addToSet: { Stories: currentObject } },
upsert: true
});
});
This code is working fine if i remove the _id:new ObjectID().toHexString()
What i want to achieve here is that for every new story i want a unique _id object to be attached to it
What am i doing wrong?
{
"_id": {
"$oid": "55ae24016fb73f6ac7c2d640"
},
"Name": "some name",
...... some other details
"Stories": [
{
"userId": "105304831528398207103",
"story": "some story"
},
{
"userId": "105304831528398207103",
"story": "some story"
}
]
}
This is the document model, the _id that i am trying to create is for the stories
You should not be calling .toHexString() on this as you would be getting a "string" and not an ObjectID. A string takes more space than the bytes of an ObjectId.
var async = require('async'),
mongo = require('mongodb'),
db = require('monk')('localhost/test'),
ObjectID = mongo.ObjectID;
var coll = db.get('junk');
var obj = { "_id": new ObjectID(), "name": "Bill" };
coll.findAndModify(
{ "_id": new ObjectID() },
{ "$addToSet": { "stories": obj } },
{
"upsert": true,
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So that works perfectly for me. Noting the "new" option there as well so the modified document is returned, rather than the original form of the document which is the default.
{ _id: 55c04b5b52d0ec940694f819,
stories: [ { _id: 55c04b5b52d0ec940694f818, name: 'Bill' } ] }
There is however a catch here, and that is that if you are using $addToSet and generating a new ObjectId for every item, then that new ObjectId makes everything "unique". So you would keep adding things into the "set". This may as well be $push if that is what you want to do.
So if userId and story in combination already make this "unique", then do this way instead:
coll.findAndModify(
{
"_id": docId,
"stories": {
"$not": { "$elemMatch": { "userId": userId, "story": story } }
}
},
{ "$push": {
"stories": {
"userId": userId, "story": story, "_id": new ObjectID()
}
}},
{
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So test for the presence of the unique elements in the array, and where they do not exist then append them to the array. Also noting there that you cannot do an "inequality match" on the array element while mixing with "upserts". Your test to "upsert" the document should be on the primary "_id" value only. Managing array entries and document "upserts" need to be in separate update operations. Do not try an mix the two, otherwise you will end up creating new documents when you did not intend to.
By the way, you can generate an ObjectID just using monk.
var db = monk(credentials.database);
var ObjectID = db.helper.id.ObjectID
console.log(ObjectID()) // generates an ObjectID

Resources