Mongoose findByIdAndUpdate method not incrementing document value - javascript

I am trying to update the like count of job posts in my application using the $inc operator and the findByIdAndUpdate method from Mongoose. The correct document is being retrieved and returned but the like count for the job post never moves up from 0 and no updates are performed in the database collection.
Below is the code I am currently using to perform the update.
Jobs.findByIdAndUpdate(req.params._id , { $inc: {likes: 1}} , {new: true})
.then((ret) => {
res.send(ret)
})
Job schema
const mongoose = require('mongoose');
let JobSchema = mongoose.Schema({
student_name: {
type: String,
required: true
},
subject: {
type: String,
required: true
},
grade: {
type: Number,
required: true
},
area: {
type: String,
required: true
},
desc: {
type: String,
required: true
},
accepted: {
type: Boolean,
required: true,
default: false
},
tutor_name: {
type: String,
required: false
},
} , {collection: 'jobs'});
let Job = mongoose.model('Job' , JobSchema);
module.exports = Job;
Any insight into what is preventing the update from performing would be greatly appreciated
I have tried using two queries, one to retrieve the current likes of a post and another to manually update the doc with the incremented value. I have tried nesting the queries using .then() statements and have also tried the updateOne() and findOneAndUpdate() methods as alternatives. I have tried experimenting with the $set and $inc operators in my queries to see if either of them perform the changes, but neither do.
I am expecting the incremented 'likes' value to be reflected in the database and for the updated job document to be returned and echoed back to the console.

Your schema is missing a likes field, which means Mongoose will remove it from queries/updates (since it doesn't match the schema). Make sure to add it:
likes: {
type: Number,
default: 0
}

Can you try something like this (adding [] around update):
Jobs.findByIdAndUpdate(req.params._id , [{ $inc: {likes: 1}}] , {new: true})
.then((ret) => {
res.send(ret)
})
EDITED:
Jobs.findByIdAndUpdate(req.params._id , [{ $set: {likes: "$likes"+1}}] , {new: true})
.then((ret) => {
res.send(ret)
})

Related

MongoDB - update specific array element by id [duplicate]

This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 3 days ago.
Ive been trying these since yesterday but I couldnt make it.
I have 2 different Schemas, the User Schema and the Vital Signs Schema. The Vital Signs Schema is inside the userSchema as an array. What i need to do is to edit an specific vitalSigns object with an unique Id
Here is a postman example of the info i need to update
const userSchema = new mongoose.Schema({
email:{type: String, required: true},
password:[{type: String, required: true}],
confirmPassword:[{type: String, required: true}],
personalData: personalDataSchema,
vitalSigns: [vitalSignsSchema],
})
const vitalSignsSchema = new mongoose.Schema({
systolic: { type: 'Number', required: false },
diastolic: { type: 'Number', required: false },
temperature: { type: 'Number', required: false },
pulse: { type: 'Number', required: false },
rate: { type: 'Number', required: false },
blood: { type: 'Number', required: false },
date: { type: 'Date', required: false },
})
My route is catching both userId and vitalSigns because i tried to make it work with a lot of ways but i couldn't
Router.post('/user/:userId/vitalSignsEdit/:vitalId', async (req, res) => {
userServices.editVitalSigns(req.params.userId,req.params.vitalId, req.body)
})
Here is one example of a way that I tried to use to make it work but instead of updating the specific vitalSigns Id, it created another object of vitalSigns with the same Id that i tried to change.
User.findById(userId)
.then( user => {
if(user) {
if(user.vitalSigns.find( e => e._id === data._id)) {
User.updateOne({_id: userId}, { vitalSigns: [...user.vitalSigns, data] })
.then(e => console.log(e), i => console.log(i))
}
}
})
Hope someone could help me with these.
You could use $push
User.updateOne({_id: userId}, { $push: { vitalSigns: data } })
Use $ to access the array element matched.
db.collection.update({
"vitalSigns._id": "vs1"
},
{
$set: {
"vitalSigns.$.modified": true
}
})
Mongo Playground

Is there a way to remove sub-document from object without passing the parentId?

Is there a way to remove sub-document from object without passing the parentId?
So I have a document which looks like this:
name: {
type: [String],
trim: true,
required: [true, 'Please add your name']
},
experience: [
{
title: {
type: String,
trim: true,
required: [true, 'Please add an experience title']
}
}
],
Each of this document can have several objects in the experience array.
Adding and updating has been easy for me because I usually just need to pass the Id of the parent document.
Now I would like to delete any object of said experience array by proving only its id...not the parentId.
Hopefull this code can help explain what I'm looking for:
const resume = await Resume.findByIdAndUpdate(
{
// _id: req.params.id, // <=== is the parent id...
experience: { _id: req.params.exp_id }
},
{
$pull: { experience: { _id: req.params.exp_id } }
},
{
new: true,
runValidators: true
}
);
You can see in the code above that I passed the _id of the parent document which if I continue this way, it works great but for my needs I need to only pass the _id of the subdocument.
Is that actually possible?
Yes you can, using {} searches in all parent documents.
parent.updateMany({}, {$pull, {children: {_id: id}}})
Please also make sure the child _id is a string and not ObjectId. If that's the case you need to first convert your string to ObjectId

Is it possible to filter and bring back only one of each notificationType/postId?

I have a notification model with this Schema here:
const NotificationSchema = new Schema({
type: {
type: String,
required: true
},
createdAt: {
type: Number,
required: true
},
postId: {
type: Schema.Types.ObjectId,
ref: 'post',
required: true
},
recipients: [{
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}]
});
I'm building a route controller that will get all notifications where they are one of the recipients here:
getNotifications(req, res, next) {
const userId = req.params.id;
Notification.find({ recipients: userId })
.sort({ createdAt: 1 })
.then(notifications => res.send(notifications))
.catch(next);
},
I also want to make sure that if there are several notifications that have the same type, such as postAuthorNotification AND the same postId, then it only brings back the notification that has the latest createdAt timestamp.
The reason for this is that if a users' post gets commented on 20 times. There will be a notification for each. I only want to send the user back ONE notification though, the latest one.
Is this possible at all with mongo/mongoose?
Thank you.
You already have the .sort() method chained, which should give you results sorted by createdAt. To limit to only 1 result, simply chain a .limit(1) to the query.
Official documentation for the limit method - http://mongoosejs.com/docs/api.html#query_Query-limit

Node.js API return different values for the same request

I'm learning to use Node.js + Express to build a REST API. In this API
I have the following method:
apiRouter.route('/training/session/byId/:id_session')
// ===== GET =======
.get(function(req, res) {
//Get the session
Session.findById(req.params.id_session, function(err, session) {
//Get an array of exercise associated with the session
Exercise.find({session: session._id}, function(err, exercise) {
let movements = [];
let sets = [];
let i = exercise.length-1;
//For every exercise get the movements and the sets
exercise.forEach(function (ex,index) {
Movement.findById(ex.movement,function(err,movement){
if(movement)
movements.push(movement);
//***** Here?
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
})
})
});
});
})
The idea is obtain all the session related information from the database.
First:
I think that is not the correct way of make multiple querys and return an object with the info of all the querys, but I'm novice with the async working of Node... So what is the correct way to make multiple querys where the data of one query depends of other query?
Second: In the Front-End (React + Redux) I'm making Ajax request with axios and for the same Ajax request sometimes not all 'sets' are fetched (//***** Here?). The problem is in the API?
Thanks in advance.
EDIT: DB models
Session:
var SessionSchema = new Schema({
date: {type: Date, default: Date.now },
time: Number, //Time in seconds
user: {required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User'},
});
Exercise:
var ExerciseSchema = new Schema({
session: {type: mongoose.Schema.Types.ObjectId, ref: 'Session'},
movement: {type: mongoose.Schema.Types.ObjectId, ref: 'Movement'},
timestamp: {
type: Date,
default: Date.now
}
});
Set:
var SetSchema = new Schema({
repetitions: Number,
weight: Number,
rest: Number,
timestamp: {
type: Date,
default: Date.now
},
exercise : {type: mongoose.Schema.Types.ObjectId, ref: 'Exercise'}
});
Movement:
var MovementSchema = new Schema({
name: { type: String, required: true, index: true, unique: true },
material:{ type: String, required: true},
muscles : [{
name : { type: String, required: true},
percentage : { type: Number, required: true}
}]
});
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
}).then(function(set){
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
Of course my prev answer wouldn't work. The set query callback would execute after the if(index == i). Actually I'm not sure this will produce different results from your code. I've never actually used Mongoose but as far as I know, you can't do joins so nested queries is the way to do it.
You might want to consider using promises. Not necessary, but they make your code easier to read and think about: http://eddywashere.com/blog/switching-out-callbacks-with-promises-in-mongoose/
It might make more sense as well to create a single result object that you build up as the queries return so you end up sending a single JSON object representing your session that looks like:
{
exercises: [
{
sets: [],
movements: [],
},
{
sets: [],
movements: [],
},
...
]
}

MongoDB query on populated fields

I have models called "Activities" that I am querying for (using Mongoose). Their schema looks like this:
var activitySchema = new mongoose.Schema({
actor: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
recipient: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
timestamp: {
type: Date,
default: Date.now
},
activity: {
type: String,
required: true
},
event: {
type: mongoose.Schema.ObjectId,
ref: 'Event'
},
comment: {
type: mongoose.Schema.ObjectId,
ref: 'Comment'
}
});
When I query for them, I am populating the actor, recipient, event, and comment fields (all the references). After that, I also deep-populate the event field to get event.creator. Here is my code for the query:
var activityPopulateObj = [
{ path: 'event' },
{ path: 'event.creator' },
{ path: 'comment' },
{ path: 'actor' },
{ path: 'recipient' },
{ path: 'event.creator' }
],
eventPopulateObj = {
path: 'event.creator',
model: User
};
Activity.find({ $or: [{recipient: user._id}, {actor: {$in: user.subscriptions}}, {event: {$in: user.attending}}], actor: { $ne: user._id} })
.sort({ _id: -1 })
.populate(activityPopulateObj)
.exec(function(err, retrievedActivities) {
if(err || !retrievedActivities) {
deferred.reject(new Error("No events found."));
}
else {
User.populate(retrievedActivities, eventPopulateObj, function(err, data){
if(err) {
deferred.reject(err.message);
}
else {
deferred.resolve(retrievedActivities);
}
});
}
});
This is already a relatively complex query, but I need to do even more. If it hits the part of the $or statement that says {actor: {$in: user.subscriptions}}, I also need to make sure that the event's privacy field is equal to the string public. I tried using $elemMatch, but since the event has to be populated first, I couldn't query any of its fields. I need to achieve this same goal in multiple other queries, as well.
Is there any way for me to achieve this further filtering like I have described?
The answer is to change your schema.
You've fallen into the trap that many devs have before you when coming into document database development from a history of using relational databases: MongoDB is not a relational database and should not be treated like one.
You need to stop thinking about foreign keys and perfectly normalized data and instead, keep each document as self-contained as possible, thinking about how to best embed relevant associated data within your documents.
This doesn't mean you can't maintain associations as well. It might mean a structure like this, where you embed only necessary details, and query for the full record when needed:
var activitySchema = new mongoose.Schema({
event: {
_id: { type: ObjectId, ref: "Event" },
name: String,
private: String
},
// ... other fields
});
Rethinking your embed strategy will greatly simplify your queries and keep the query count to a minimum. populate will blow your count up quickly, and as your dataset grows this will very likely become a problem.
You can try below aggregation. Look at this answer: https://stackoverflow.com/a/49329687/12729769
And then, you can use fields from $addFields in your query. Like
{score: {$gte: 5}}
but since the event has to be populated first, I couldn't query any of its fields.
No can do. Mongodb cannot do joins. When you make a query, you can work with exactly one collection at a time. And FYI all those mongoose populates are additional, distinct database queries to load those records.
I don't have time to dive into the details of your schema and application, but most likely you will need to denormalize your data and store a copy of whatever event fields you need to join on in the primary collection.

Categories

Resources