I have a posts collection that has array of likes.I want to push object into likes array if user have not liked and pull if user has liked the post.I test my API but it always update first document of collection though I provided postId of other document.
schema.js
likes: [
{
userId: String,
isNotified: {
type: Boolean,
default: false,
},
email: String,
types: String,
},
],
API
router.post("/like", (req, res) => {
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
// console.log(doc)
if (!doc.length) {
postModel.updateOne(
{ "_Id": req.body.postId,},
{
$push: {
likes: {
userId: req.body.userId,
email: req.body.email,
// types: req.body.types,
},
},
},
(err, doc) => {
res.send("like");
}
);
} else {
// console.log("pull")
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
doc.map((e) => {
e.likes.map((x) => {
if (x.userId == req.body.userId) {
postModel.updateOne(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
{
$pull: {
likes: {
userId: req.body.userId,
email:req.body.email
},
},
},
(err, doc) => {
res.send("unlike");
}
);
}
});
});
}
);
}
// res.send(doc);
}
);
// });
});
postman request
{
"email":"mahima#gmail.com",
"types":"like",
"postId":"6312c2d1842444a707b6902f",
"userId":"631452d0e1c2acf0be28ce43"
}
How to fix this,suggest an advice.Thanks in advance.
I'm not sure if I undrestand the logic, but here are couple of things that I think you can improve:
You are using find method to get a single document, you should use findOne method which return a single document (if exists) and not an array of documents. But in general when you have the _id value of a document, it's better to just use findById method which is much faster.
When you find a document, you can just modify it and call it's save method to write your changes to the database, there is no need to use updateOne. (please note that partital update has many advantages but in your case they don't seem necessary, you can read about it online.)
your API code can be something like this:
router.post("/like", (req, res) => {
const postId = req.body.postId
const userId = req.body.userId
postModel.findById(postId) // get the post
.then(post => {
if (post) { // check if post exists
// check if user has already liked the post
if (post.likes.find(like => like.userId == userId)){
// user has already liked the post, so we want to
// remove it from likes (unlike the post).
// I know this is not the best way to remove an item
// from an array, but it's easy to understand and it
// also removes all duplications (just in case).
post.likes = post.likes.filter(like => like.userId != userId)
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("unlike")
})
} else {
// user has not liked the post, so we want to add a
// like object to post's likes array
post.likes.push({
userId: userId,
email: req.body.email // you can other properties here
})
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("like")
})
}
} else { // in case post doesn't exist
res.status(404).send("post not found.")
}
})
.catch(err => {
// you can handle errors here
console.log(err.message)
res.send("an error occurred")
})
})
I didn't run the code, but it should work.
Related
I have an app that allows users to rate books. The books are called from Google Books API. I only save a copy of the book in my DB when the user submits their rating.
reviews.put("/:id/new", async (req, res) => {
let url = await `https://www.googleapis.com/books/v1/volumes/${req.params.id}`;
console.log(url);
await request(url, { json: true }, async (error, response, data) => {
let newRating;
Book.findOne({ id: req.params.id }, (err, result) => {
if (err) console.log(err.message);
if (result) {
if (req.body.stars !== undefined) {
newRating = /* some math formula */
} else {
newRating = /* some math formula */
}
} else {
newRating = req.body.stars;
}
});
Book.findOneAndUpdate(
{
id: req.params.id
},
{
id: data.id,
id: data.id,
title: data.volumeInfo.title,
description: data.volumeInfo.description,
img: data.volumeInfo.imageLinks.thumbnail,
author: data.volumeInfo.authors,
rating: newRating,
$inc: {
ratingCount: 1
}
},
{
upsert: true,
returnNewDocument: true
},
(err, book) => {
console.log(book) // If its creating a new document, book returns null. If the book is already in the DB, book returns the document.
Review.create({
rating: req.body.stars,
review: req.body.review,
reviewer: req.session.currentUser._id,
book: book._id // <-- ERROR, cannot read property "_.id" of null
});
}
);
});
res.redirect("/");
});
The issue is that book returns null when it's newly created. But this works fine if someone else has already rated it. I've tried using .save() but that did not work. How else can I get the _.id of the newly created book?
Any help is appreciated. Thanks!
You are passing through the incorrect query options. You need to be using:
new: bool - if true, return the modified document rather than the original.
upsert: bool - creates the object if it doesn't exist. defaults to false.
Update the method as follows:
Book.findOneAndUpdate(
{
id: req.params.id
},
{
id: data.id,
id: data.id,
title: data.volumeInfo.title,
description: data.volumeInfo.description,
img: data.volumeInfo.imageLinks.thumbnail,
author: data.volumeInfo.authors,
rating: newRating,
$inc: {
ratingCount: 1
}
},
{
upsert: true,
new: true
},
(err, book) => {
console.log(book) // If its creating a new document, book returns null. If the book is already in the DB, book returns the document.
Review.create({
rating: req.body.stars,
review: req.body.review,
reviewer: req.session.currentUser._id,
book: book.id // <-- ERROR, cannot read property "_.id" of null
});
}
See the docs
Use 'new': true like this: { upsert: true, 'new': true }.
This should return upserted document.
I have this following Mongoose User Schema:
postCreated:{
type: Array,
default: []
}
which contains an Array of Object of Posts belong to that user. I plan to do the following: When I delete a particular post, I pass the id of that post and the username of the user that created to the back-end and hope that it would remove the post from both Post schema and postCreated of a user that it belongs to
server.del('/posts',(req,res,next)=>{
const {id,username} = req.body;
User.findOne({username}).then(user => {
console.log(user.postCreated)
user.postCreated.filter(post => {
post._id !== id;
});
console.log(user.postCreated)
});
Posts.findOneAndRemove({_id: id}).then((post) => {
if(!post){
return next(new errors.NotFoundError('Post not found'));
}
res.send(post);
})
.catch((e) => {
return next(new errors.BadRequestError(e.message));
});
});
However, the post only got removed from the Post Model, and not from postCreated of User Model, meaning that the user.postCreated.filter did not work.
I have tried the following, thanks to Jack, but seems like it did not solve the issue:
User.update(
{ username },
{ $pull: { postCreated: {_id: id} } },
{ multi: true }
);
Is there any way that I could fix this?
I would really appreciate any help.
If you want to do it the way you were doing you need to store back your postCreated array into it self and then save the user:
User.findOne({username}).then(user => {
console.log(user.postCreated)
user.postCreated = user.postCreated.filter(post => {
post._id !== id;
});
console.log(user.postCreated);
user.save();
});
But the best way is findOneAndUpdate if you need the user object later on.
You can use mongoose $pull
Use: https://docs.mongodb.com/manual/reference/operator/update/pull/
User.update(
{ username },
{ $pull: { postCreated: id } },
{ multi: true }
);
This should solve your query.
I'm trying to update the subdocument within the array without success. The new data doesn't get saved.
Express:
router.put('/:id/:bookid', (req, res) => {
library.findOneAndUpdate(
{ "_id": req.params.id, "books._id": req.params.bookid},
{
"$set": {
"title.$": 'new title'
}
}
});
LibraryScema:
const LibarySchema = new Library({
Name: {
type: String,
required: false
},
books: [BookSchema]
});
bookScema:
const BookSchema = new Schema({
title: {
type: String,
required: false
},
Chapters: [
{
chapterTitle: {
type: String,
required: false
}
}
]
});
I only aim to update the sub-document, not parent- and sub-document at same time.
I had a similar issue. I believe there is something wrong with the $set when it comes to nested arrays (There was an entire issue thread on GitHub). This is how I solved my issue.
var p = req.params;
var b = req.body;
Account.findById(req.user._id, function (err, acc) {
if (err) {
console.log(err);
} else {
acc.websites.set(req.params._id, req.body.url); //This solved it for me
acc.save((err, webs) => {
if (err) {
console.log(err);
} else {
console.log('all good');
res.redirect('/websites');
}
});
}
});
I have a user with a nested array.
Try this code
router.put('/:id/:bookid', (req, res) => {
library.findById(
req.params.id, (err, obj) => {
if (err) console.log(err); // Debugging
obj.books.set(req.params.bookid, {
"title": 'new title',
'Chapters': 'your chapters array'
});
obj.save((err,obj)=>{
if(err) console.log(err); // Debugging
else {
console.log(obj); // See if the saved object is what expected;
res.redirect('...') // Do smth here
}
})
})
});
Let me know if it works, and I'll add explanation.
Explanation: You start by finding the right object (library in this case), then you find the correct object in the array called books.
Using .set you set the whole object to the new state. You'll need to take the data that's not changing from a previous instance of the library object.
I believe this way will overwrite and remove any data that's not passed into the .set() method. And then you save() the changed.
I have the following mongoose schema:
user = {
"userId" : "myId",
"connections":
[{
"dateConnectedUnix": 1334567891,
"isActive": true
}, {
"dateConnectedUnix": 1334567893,
"isActive": false
}]
}
I would like to delete the second item in the connections array, to get the following:
user = {
"userId" : "myId",
"connections":
[{
"dateConnectedUnix": 1334567893,
"isActive": false
}]
}
The following code does the job as expected:
userAccounts.update(
{ 'connections.isActive': false },
{ $pull: { 'connections.isActive':false }},
function (err, val) {
console.log(val)
}
);
But, I need to delete based on ObjectId. And the following goes does not work:
userAccounts.update(
{ 'connections._id': '1234-someId-6789' },
{ $pull: { 'connections._id': '1234-someId-6789' } },
function (err, val) {
console.log(val)
}
);
Any suggestions? I have been banging my head against the screen (aka Google, Stackoverflow, ...) for hours and have had no luck.
It seems that the above code would not work. It should not even have worked for the first example I gave.
In the end I was supported by this answer here: MongoDB, remove object from array
Here is my working code:
userAccounts.update(
{ userId: usr.userId },
{
$pull: {
connections: { _id : connId }
}
},
{ safe: true },
function removeConnectionsCB(err, obj) {
// ...
}
);
I have a document like
I have to delete address from address array
After searching lots on internet I found the solution
Customer.findOneAndUpdate(query, {$pull: {address: addressId}}, (err, data) => {
if (err) {
return res.status(500).json({ error: 'error in deleting address' });
}
res.json(data);
});
user: {
_id: ObjectId('5ccf3fa47a8f8b12b0dce204'),
name: 'Test',
posts: [
ObjectId("5cd07ee05c08f51af8d23b64"),
ObjectId("5cd07ee05c08f51af8d23c52")
]
}
Remove a single post from posts array
user.posts.pull("5cd07ee05c08f51af8d23b64");
user.save();
To use update with ObjectId, you should use ObjectId object instead of string representation :
var ObjectId = require('mongoose').Types.ObjectId;
userAccounts.update(
{ 'connections._id': new ObjectId('1234-someId-6789') },
{ $pull: { 'connections._id': new ObjectId('1234-someId-6789') } },
function (err,val) {
console.log(val)
}
);
use findByIdAndUpdate to remove an item from an array
You can do it in mongoose 5.4.x and above
const result = await User.findByIdAndUpdate(user_id, {
$pull: {
someArrayName: { _id: array_item_id }
}
}, { new: true });
The item from array will be removed based on provided property _id value
If you are using mongoose, no need to use the MongoDB stuff, I mean that's why we're using mongoose in the first place, right?
userAccounts.connections.pull({ _id: '1234-someId-6789'});
await userAccounts.save();
mongoose: 4.11.11
What have worked for me is the following syntax:
const removeTansactionFromUser = (userId, connectionId) => {
return User.findByIdAndUpdate(userId, { $pull: { "connections": connectionId} }, {'new': true} );
};
Mongoose support id in string format or ObjectId format.
Tip: new ObjectId(stringId) to switch from string to ObjectId
In mongoose 5.8.11, this $pull: { ... } didn't work for me, so far not sure why. So I overcame it in my controller this way:
exports.removePost = async (req, res, next) => {
const postId = req.params.postId;
try {
const foundPost = await Post.findById(postId);
const foundUser = await User.findById(req.userId);
if (!foundPost || !foundUser) {
const err = new Error(
'Could not find post / user.',
);
err.statusCode = 404;
throw err;
}
// delete post from posts collection:
await Post.findByIdAndRemove(postId);
// also delete that post from posts array of id's in user's collection:
foundUser.posts.pull({ _id: postId });
await foundUser.save();
res.status(200).json({ message: 'Deleted post.' });
} catch (err) {
// ...
}
};
I have an array in my model document. I would like to delete elements in that array based on a key I provide and then update MongoDB. Is this possible?
Here's my attempt:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var favorite = new Schema({
cn: String,
favorites: Array
});
module.exports = mongoose.model('Favorite', favorite, 'favorite');
exports.deleteFavorite = function (req, res, next) {
if (req.params.callback !== null) {
res.contentType = 'application/javascript';
}
Favorite.find({cn: req.params.name}, function (error, docs) {
var records = {'records': docs};
if (error) {
process.stderr.write(error);
}
docs[0]._doc.favorites.remove({uid: req.params.deleteUid});
Favorite.save(function (error, docs) {
var records = {'records': docs};
if (error) {
process.stderr.write(error);
}
res.send(records);
return next();
});
});
};
So far it finds the document but the remove nor save works.
You can also do the update directly in MongoDB without having to load the document and modify it using code. Use the $pull or $pullAll operators to remove the item from the array :
Favorite.updateOne({ cn: req.params.name }, {
$pullAll: {
favorites: req.params.deleteUid,
},
});
To remove objects from array then
Favorite.updateOne({ cn: req.params.name }, {
$pullAll: {
favorites: [{_id: req.params.deleteUid}],
},
});
(you can also use updateMany for multiple documents)
http://docs.mongodb.org/manual/reference/operator/update/pullAll/
The checked answer does work but officially in MongooseJS latest, you should use pull.
doc.subdocs.push({ _id: 4815162342 }) // added
doc.subdocs.pull({ _id: 4815162342 }) // removed
https://mongoosejs.com/docs/api.html#mongoosearray_MongooseArray-pull
I was just looking that up too.
See Daniel's answer for the correct answer. Much better.
Answers above are shown how to remove an array and here is how to pull an object from an array.
Reference: https://docs.mongodb.com/manual/reference/operator/update/pull/
db.survey.update( // select your doc in moongo
{ }, // your query, usually match by _id
{ $pull: { results: { $elemMatch: { score: 8 , item: "B" } } } }, // item(s) to match from array you want to pull/remove
{ multi: true } // set this to true if you want to remove multiple elements.
)
Since favorites is an array, you just need to splice it off and save the document.
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var favorite = new Schema({
cn: String,
favorites: Array
});
module.exports = mongoose.model('Favorite', favorite);
exports.deleteFavorite = function (req, res, next) {
if (req.params.callback !== null) {
res.contentType = 'application/javascript';
}
// Changed to findOne instead of find to get a single document with the favorites.
Favorite.findOne({cn: req.params.name}, function (error, doc) {
if (error) {
res.send(null, 500);
} else if (doc) {
var records = {'records': doc};
// find the delete uid in the favorites array
var idx = doc.favorites ? doc.favorites.indexOf(req.params.deleteUid) : -1;
// is it valid?
if (idx !== -1) {
// remove it from the array.
doc.favorites.splice(idx, 1);
// save the doc
doc.save(function(error) {
if (error) {
console.log(error);
res.send(null, 500);
} else {
// send the records
res.send(records);
}
});
// stop here, otherwise 404
return;
}
}
// send 404 not found
res.send(null, 404);
});
};
This is working for me and really very helpful.
SubCategory.update({ _id: { $in:
arrOfSubCategory.map(function (obj) {
return mongoose.Types.ObjectId(obj);
})
} },
{
$pull: {
coupon: couponId,
}
}, { multi: true }, function (err, numberAffected) {
if(err) {
return callback({
error:err
})
}
})
});
I have a model which name is SubCategory and I want to remove Coupon from this category Array. I have an array of categories so I have used arrOfSubCategory. So I fetch each array of object from this array with map function with the help of $in operator.
keywords = [1,2,3,4];
doc.array.pull(1) //this remove one item from a array
doc.array.pull(...keywords) // this remove multiple items in a array
if you want to use ... you should call 'use strict'; at the top of your js file; :)
I used this format for my project and it's worked
router.delete('/dashboard/participant/:id', async (req, res, next) => {
try {
const participant = await Participant.findByIdAndDelete({ _id: req.params.id });
// { $pull: { templates: { _id: templateid } } },
const event = await Event.findOneAndUpdate({ participants: participant._id }, { $pull: { participants: participant._id } }, { new: true });
res.status(200).json({ request: 'Deleted', participant, event });
} catch (error) {
res.json(error)
}
});
Favorite.update({ cn: req.params.name }, { "$pull": { "favorites": { "_id": favoriteId } }}, { safe: true, multi:true }, function(err, obj) {
//do something smart
});