Having trouble incrementing objects inside array in with moongose - node.js

So I've got an object that looks like this
{"_id":"5fb07ab6215679200cef0eb1","user":{"_id":"5fb07437538fcd2870e21a8e","email":"example#example.com","id":"5fb07437538fcd2870e21a8e"},"question":"question?","answers":[{"_id":"5fb07ab6215679200cef0eb2","answer":"Yes","votes":0}],"voters":[],"createdAt":"2020-11-15T00:47:50.156Z","updatedAt":"2020-11-15T00:47:50.156Z","__v":0,"id":"5fb07ab6215679200cef0eb1"}
and I'm trying to increase the votes variable by this function using findOneAndUpdate
export const castVote = async (id, answersid) =>
Poll.findOneAndUpdate(
{ id, 'answers._id': answersid },
{ $inc: { 'answers.$.votes': 1 } }
);
As far as i can see calling castVote("5fb07ab6215679200cef0eb1", "5fb07ab6215679200cef0eb2") works as in not crashing the server and not giving any errors back, but the votes variable in the answers object doesn't increase so something must be wrong. Is there something obvious I'm missing here?.

got it working by simply dropping the id field which i guess i enough since they're uniquely created

Related

Watch MongoDB to return changes along with a specified field value instead of returning fullDocument

I'm using watch() function of mongo to listen to changes made to a replicaSet, now I know I can get the whole document (fullDocument) by passing { fullDocument: 'updateLookup' } to the watch method,
like :-
someModel.watch({ fullDocument: 'updateLookup' })
But what I really want to do is, get just one extra field which isn't changed every time a new update is made.
Let's say a field called 'user_id', currently I only get the updatedFields and the fullDocument which contains the 'user_id' along with a lot of other data which I would like to avoid.
What I have researched so far is Aggregation pipeline but couldn't figure out a way to implement it.
Can anybody help me figure out a way to this?
Thanks everyone for suggesting, as #D.SM pointed out I successfully implemented $project
Like this :-
const filter = [{"$match":{"operationType":"update"}}, {"$project":{"fullDocument.user_id": 1, "fullDocument.chats": 0, "fullDocument._id": 0, "fullDocument.first_name": 0, "fullDocument.last_name": 0 }}];
Then passed it to watch() method
Like:-
const userDBChange = userChatModel.watch(filter, { fullDocument: 'updateLookup' });
Now I'm only getting user_id inside fullDocument object when the operationType is update hence reducing the data overhead returned from mongo
Thanks again #D.SM and other's for trying to help me out ;)

Mongoose's findByIdAndUpdate not changing database's state

I have a strange problem. I want to update a document in my MongoDB with the mongoose.findByIdAndUpdate method, but it seems not to be working. The code is:
Device.findByIdAndUpdate(
req.params.id,
{ $set: { power: power } },
{ new: true },
(err, device) => { ... }
I get no error, but the device returned in the callback does not have the updated value. At first I thought maybe it was some sort of problem with the { new: true } option that tells mongoose to return the updated document, but then I checked the database, and the value there also has not been updated.
I also tried replacing findByIdAndUpdate with update function, but the results are the same - the db is not getting updated.
If it changes anything I use mongoose.update() function in other places and it works fine. I also tried the 'classical' way of updating the value here - meaning I used findOne function and then changed returned document's power field value and saved it and it also worked fine.
I will be really gratefull for any advice on fixing this. Thank you!

Sequelize increment function returning error

Trying to increment an integer field on a model instance in my DB. Here is the relevant code.
models.Options.findAll({
where: {
PollId: poll_id,
name: option_to_update
}
}).then((option) => {
option.increment('votes');
res.json(option);
});
When I console.log(option), it shows as an Instance so I know it inherits from the Instance class which has an increment function as can be seen here
https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/instance.js#L934
However, when I try to run option.increment, I get this back
Unhandled rejection TypeError: option.increment is not a function
Not really sure what I'm doing wrong.
findAll() will return an array of results, so if you want to increment the field, you should use option[0].increment('votes') (assuming you want to update only the first result).
Or, if you know there's going to be at most one result, you could use findOne instead of findAll.
Because incrementing is done entirely server side, if you want to retrieve the current version of the document in the database after incrementing, you need to reload the instance first.
I think this would be the appropriate way of doing that:
models.Options.findOne({
where: {
PollId: poll_id,
name: option_to_update
}
}).then(option => {
return option.increment('votes'); // assumes `option` always exists
}).then(option => {
return option.reload();
}).then(option => {
res.json(option);
});
(of course, you could take a shortcut and assume that votes will be its current value + 1 after incrementing, but in a highly concurrent situation that might not always be the case)

Mongodb, incrementing value inside an array. (save() ? update() ?)

var Poll = mongoose.model('Poll', {
title: String,
votes: {
type: Array,
'default' : []
}
});
I have the above schema for my simple poll, and I am uncertain of the best method to change the value of the elements in my votes array.
app.put('/api/polls/:poll_id', function(req, res){
Poll.findById(req.params.poll_id, function(err, poll){
// I see the official website of mongodb use something like
// db.collection.update()
// but that doesn't apply here right? I have direct access to the "poll" object here.
Can I do something like
poll.votes[1] = poll.votes[1] + 1;
poll.save() ?
Helps much appreciated.
});
});
You can to the code as you have above, but of course this involves "retrieving" the document from the server, then making the modification and saving it back.
If you have a lot of concurrent operations doing this, then your results are not going to be consistent, as there is a high potential for "overwriting" the work of another operation that is trying to modify the same content. So your increments can go out of "sync" here.
A better approach is to use the standard .update() type of operations. These will make a single request to the server and modify the document. Even returning the modified document as would be the case with .findByIdAndUpdate():
Poll.findByIdAndUpdate(req.params.poll_id,
{ "$inc": { "votes.1": 1 } },
function(err,doc) {
}
);
So the $inc update operator does the work of modifying the array at the specified position using "dot notation". The operation is atomic, so no other operation can modify at the same time and if there was something issued just before then the result would be correctly incremented by that operation and then also by this one, returning the correct data in the result document.

Mongoose update multiple geospacial index with no limit

I have some Mongoose Models with geospacial indexes:
var User = new Schema({
"name" : String,
"location" : {
"id" : String,
"name" : String,
"loc" : { type : Array, index : '2d'}
}
});
I'm trying to update all items that are in an area - for instance:
User.update({ "location.loc" : { "$near" : [ -122.4192, 37.7793 ], "$maxDistance" : 0.4 } }, { "foo" : "bar" },{ "multi" : true }, function(err){
console.log("done!");
});
However, this appears to only update the first 100 records. Looking at the docs, it appears there is a native limit on finds on geospatial indices for that applies when you don't set a limit.
(from docs:
Use limit() to specify a maximum number of points to return (a default limit of 100 applies if unspecified))
This appears to also apply to updates, regardless of the multi flag, which is a giant drag. If I apply an update, it only updates the first 100.
Right now the only way I can think of to get around this is to do something hideous like this:
Model.find({"location.loc" : { "$near" : [ -122.4192, 37.7793 ], "$maxDistance" : 0.4 } },{limit:0},function(err,results){
var ids = results.map(function(r){ return r._id; });
Model.update({"_id" : { $in : ids }},{"foo":"bar"},{multi:true},function(){
console.log("I have enjoyed crippling your server.");
});
});
While I'm not even entirely sure that would work (and it could be mildly optimized by only selecting the _id), I'd really like to avoid keeping an array of n ids in memory, as that number could get very large.
Edit:
The above hack doesn't even work, looks like a find with {limit:0} still returns 100 results. So, in an act of sheer desperation and frustration, I have written a recursive method to paginate through ids, then return them so I can update using the above method. I have added the method as an answer below, but not accepted it in hopes that someone will find a better way.
This is a problem in mongo server core as far as I can tell, so mongoose and node-mongodb-native are not to blame. However, this is really stupid, as geospacial indices is one of the few reasons to use mongo over some other more robust NoSQL stores.
Is there a way to achieve this? Even in node-mongodb-native, or the mongo shell, I can't seem to find a way to set (or in this case, remove by setting to 0) a limit on an update.
I'd love to see this issue fixed, but I can't figure out a way to set a limit on an update, and after extensive research, it doesn't appear to be possible. In addition, the hack in the question doesn't even work, I still only get 100 records with a find and limit set to 0.
Until this is fixed in mongo, here's how I'm getting around it: (!!WARNING: UGLY HACKS AHEAD:!!)
var getIdsPaginated = function(query,batch,callback){
// set a default batch if it isn't passed.
if(!callback){
callback = batch;
batch = 10000;
}
// define our array and a find method we can call recursively.
var all = [],
find = function(skip){
// skip defaults to 0
skip = skip || 0;
this.find(query,['_id'],{limit:batch,skip:skip},function(err,items){
if(err){
// if an error is thrown, call back with it and how far we got in the array.
callback(err,all);
} else if(items && items.length){
// if we returned any items, grab their ids and put them in the 'all' array
var ids = items.map(function(i){ return i._id.toString(); });
all = all.concat(ids);
// recurse
find.call(this,skip+batch);
} else {
// we have recursed and not returned any ids. This means we have them all.
callback(err,all);
}
}.bind(this));
};
// start the recursion
find.call(this);
}
This method will return a giant array of _ids. Because they are already indexed, it's actually pretty fast, but it's still calling the db many more times than is necessary. When this method calls back, you can do an update with the ids, like this:
Model.update(ids,{'foo':'bar'},{multi:true},function(err){ console.log('hooray, more than 100 records updated.'); });
This isn't the most elegant way to solve this problem, you can tune it's efficiency by setting the batch based on expected results, but obviously the ability to simply call update (or find for that matter) on $near queries without a limit would really help.

Resources