Reputation: 45
I am new to MongoDB, and I'm trying to implement an upvote/downvote system so that users can vote on reviews in my application.
How I've set up my system is that the user sends an POST request via AJAX by pressing an upvote or downvote button in the application, which contains a boolean "upvote" which is an upvote if true, and a downvote if false (this code works so I didn't include it). Once the request reaches the server, the server checks if the review the user voted on contains a vote already. If not, the server adds a vote to the review's "votes" array and increments or decrements the voteBalance attribute of that review. If there already exists a vote in that review's "votes" array then it should either:
1) Modify it if the existing vote's upvote attribute is different from the new vote and then modify voteBalance accordingly, or
2) Delete the existing vote if its upvote attribute is the same as the new one and then modify voteBalance accordingly
My code for inserting a new vote works fine, but the issue I'm having is that I can't figure out how to make it work when a vote already exists. In the server-side code below, the else statement near the bottom is what I tried to handle case 1) from above, but it doesn't work. So how can I get both these cases to work?
Here is my Review schema:
var ReviewSchema = new mongoose.Schema({
authorID: {
type: String,
required: true,
},
movieID: {
type: Number,
required: true,
},
date: {
type: Number,
required: true
},
username: {
type: String,
required: true
},
score: {
type: Number,
required: true
},
text: {
type: String
},
voteBalance: {
type: Number,
required: true,
default: 0
},
votes: [{
voterID: {
type: String,
required: true,
},
upvote: {
type: Boolean,
required: true
}
}],
comments: [{
commenterID: {
type: String,
required: true,
},
text: {
type: String,
required: true
},
date: {
type: Number,
required: true
}
}]
},{collection: 'reviews'});
Here is the code I'm using to create and update votes on the server:
Review.findOne({_id: new ObjectID(reviewID), votes: { $elemMatch: { voterID: req.session._id }}}, function(err, review) {
if (err) {
console.log(err);
res.status(500).send();
}
//If vote was not found (or if review was not found), create vote
if (!review) {
if (upvote) {
var update = {$addToSet: {votes: {voterID: req.session._id, upvote}}, $inc : {voteBalance : 1}};
}
else {
var update = {$addToSet: {votes: {voterID: req.session._id, upvote}}, $inc : {voteBalance : -1}};
}
Review.findOneAndUpdate({_id: new ObjectID(reviewID)}, update, function(err, review) {
if (err) {
console.log(err);
return res.status(500).send();
}
res.status(200).send();
});
}
//If vote was found, update
else {
if (upvote) {
var update = {$set: { 'votes.$.upvote': upvote }, $inc : {voteBalance : 1}};
}
else {
var update = {$set: { 'votes.$.upvote': upvote }, $inc : {voteBalance : -1}};
}
Review.findOneAndUpdate({_id: new ObjectID(reviewID), 'votes.$.voterID': req.session._id}, update, function(err) {
if (err) {
console.log(err);
return res.status(500).send();
}
res.status(200).send();
});
}
});
Also, I recognize that this code is probably not as efficient as it could be, and I would appreciate any tips on that front as well.
Upvotes: 1
Views: 1828
Reputation: 3872
Instead of doing findOne()
and then findOneAndUpdate()
, you would be better off using findOneAndUpdate()
with the upsert option. That way you don't need that extra if statement in the callback.
I'd also recommend not storing votes
as an array in the ReviewSchema
. That array can grow without bound because any number of users can vote on a Review
, which means a review document might become huge and unwieldy. I'd recommend using a mapping collection instead.
Upvotes: 1