Issues with mongodb/nodejs/express/mongojs and findAndModify - node.js

Currently doing a online course to learn some Node.js, Express, and MongoDB.
In this course there is a api section where they teach you to do simple stuff, but i ran in to an issue, the course video shows him updating name of an item, and the api makes it possible to update more fields, his fields keep there value, my fields actually end up being null.
The code is
app.put('/products/:id', function(req, res){
db.products.findAndModify({query: {_id: mongojs.ObjectId(req.params.id)},
update:{$set:{
name: req.body.name,
category: req.body.category,
description: req.body.description
}},
new: true
}, function(err, doc){
if(err){
res.send(err);
} else {
console.log('Updating Product...');
res.json(doc);
}
});
});
Can any one explain to me how i avoid lets say category and description ending up being null if only the name is updated?

If req.body.category and req.body.description are undefined in your code:
update:{$set:{
name: req.body.name,
category: req.body.category,
description: req.body.description
}},
your fields will be set to null on the matching document.
See the mongodb set null in update and set field as empty for mongo object using mongoose

Related

Mongoose NodeJS Schema with array of ref's

I know there is allot's of answers about it but still I didn't quite get the idea.
I have CourseSchema:
const CourseSchema = new Schema({
course_name: String,
course_number: {type: String, unique : true },
enrolledStudents:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student' }]
});
And a StudentSchema:
const StudentSchema = new Schema({
first_name: String,
last_name: String,
enrolledCourses:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'CourseSchema'
}]
});
I want to reffer enrolledStudents at CourseSchema with a student, and enrolledCourses at StudentSchema with a course.
router.post('/addStudentToCourse', function (req, res) {
Course.findById(req.params.courseId, function(err, course){
course.enrolledStudents.push(Student.findById(req.params.studentId, function(error, student){
student.enrolledCourses.push(course).save();
})).save();
});
});
but when posting I get an error:
TypeError: Cannot read property 'enrolledStudents' of null
Ok so after readying Query-populate I did that:
router.post('/addStudentToCourse', function (req, res) {
Course.
findOne({ _id : req.body.courseId }).
populate({
path: 'enrolledStudents'
, match: { _id : req.body.studentId }
}).
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.course_name);
});
});
And when i'm hitting POST on postman I get on the console:
The course name is intro for cs
but it is loading for ever and later on console I get:
POST /courses/addStudentToCourse - - ms - -
You are missing the populate instruction. For example:
see more about it here
Course.
findOne({ courseId : req.params.courseId }).
populate('enrolledStudents').
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.name);
});
It is working by using the ref field that "knows" how to populate withput using the push syntax. it is like a foreign key population.
Just call the populate method on the query and an array of documents will be returned in place of the original _ids. you can learn more on the internals of the populate methods in the official docs

How do you update a referenced document in mongoose?

I'm creating a reservation system of sorts using mongoose and nodejs.
There are a list of hotels which have number of available rooms as a field.
While creating a new booking for a customer, I want to update the number of available rooms in the hotel by reducing it by 1, for example.
Here's my code:
Hotel Model File:
var hotel: new mongoose.Schema{
name: String,
availableRooms: {type: Number, default: 1}}
Booking Model File:
var booking: new mongoose.Schema{
userName: String,
hotelId: {type: Schema.Types.ObjectId, ref: 'hotel'}
}
Here's the post operation that I'm having trouble with:
api.route('/booking').post(function(req,res){
hotel.findOneAndUpdate({id: req.body.hotelId, availableRooms: {$gt: 0}},
availableRooms: -1, function(err){
if (err) throw err})
booking.create(req.body, function(err, confirmedBooking){
if (err) throw err;
res.json(confirmedBooking)
});
Postman shows this error:
ReferenceError: hotel is not defined
There are multiple errors in your code:
You might not have imported hotel schema in your node app(app.js/ server.js).
The error from postman hotel is undefined is coming because of that.
If you have already imported, please check variable name, they are case sensitive, so check that too.
To import the hotel schema in your node app.:
var Hotel = require('path/to/hotel.js');
//OR
var Hotel = mongoose.model('Hotel');
Then try updating the document using Hotel, instead of hotel.
you cant decrease the value of a field like that, you need to use $inc.
Try this:
var hotelId = mongoose.Schema.Types.ObjectId(req.body.hotelId);
// Dont forget to include mongoose in your app.js
Hotel.findOneAndUpdate({
_id: hotelId, availableRooms: {$gt: 0}
},{
$inc : { availableRooms : -1 }
}, function(err){
if (err) throw err
else
{
booking.create(req.body, function(err, confirmedBooking){
if (err) throw err;
res.json(confirmedBooking)
});
}
});
Update : I have moved the section to creat new booking inside the callback of update function, so that new booking gets created only when it is successfully updated. It's better to use this way

Trying to work out mongoose relationship coding for REST API

This is a stupid question but I have tried to wrap my head around this via Google, code snippits, tutorials, and all of them lead me to examples in which the models are too shallow for the coding I want to do.
I have an app I want to develop where data is in the form of parents and children:-
- Organisation
- Projects that belong to those organisations
- Releases that belong to those projects
and so on, but I don't fully understand how I can write a route in express that follows said hierachy and I come from an SQL relational background. Do I use cookies, or part of the route? I know how to set up the model, from what I understand, using:
var organisationSchema = ({
name: String,
email: String,
description: String,
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
});
for Organisation and
var projectSchema = ({
name: String,
description: String,
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
}
for project but then how do I set up my post route to add the project to the correct organisation
router.route('/api/project')
.post(function(req, res){
project = new Project();
project.name = req.body.name;
project.organisation = req.body.organisation;
if (err)
res.send(err);
})
project.save(function(err){
if (err)
res.send(err);
res.json({ message: 'Project ' + project.name + ' created.'});
})
})
Do I need a cookie to populate the organisation in this example?
If your projects belong to organizations, you'll either want to include an array of objectIds in your organization schema which will contain project IDs or a field in your project schema that will contain the relevant organization ID.
You can send the proper organization either in the body, as you are, or in the URL parameters.
For example, something similar to this:
router.route('/api/:organizationId/project')
.post(function(req, res, next) {
var project = new Project({
name: req.body.name,
organization: req.params.organizationId
});
if(err) { return next(err); }
project.save(function(err, savedProject) {
if(err) { return next(err); }
return res.status(200).json({ message: "Project " + savedProject.name + " created." });
}
}
If you pay attention to the communication/messaging model and routes in this repository, it might help: https://github.com/joshuaslate/mern-starter/tree/master/server

Using $or in a FindOne query - MongoDB

UPDATE: Changed if (!result.length) to if (!result) as I'm using .findOne and that seems to be working so far. If you spot anything else that can be improved within the code snippet that would be awesome!
Just starting to learn Node JS and MongoDB (using Mongoose) so excuse me if I'm totally out of context.
Im trying to find a row in my MongoDB with the following query:
exports.findById = function(req, res) {
var id = req.params.id;
Team.findOne({'teamid':id, $or:[{'creator':req.user.id}, {userlist: { $in : [req.user.id]}}]}, function(err, result) {
if (err) console.log(err);
if (!result.length)
res.redirect('/');
else
res.render('team', { team : result.teamid });
});
};
I want to retrieve a row that has the field teamid equal to id as well as to check if the field creator is equal to req.user.id OR if req.user.id is in the userlist field. I am expecting only one result from the above query.
Note that this query works just fine, but I just need to look inside the userlist array:
Team.findOne({'teamid':id, 'creator':req.user.id}, function(err, result) {...
And finally the Team schema
var Team = new Schema({
team_name: { type: String, required: true, trim: true},
teamid: { type: String },
creator: String,
created_at: Date,
userlist: Array
});
Any help to figure out what's the problem is greatly appreciated!

Mongoose - REST API - Schema With Query to different model

I'm trying to avoid DB Callback Queries.
Assuming that you have two schemas that looks like so :
1st) User Schema
username : {type: String, unique: true},
age : {type: Number}
2nd) Activity Schema
owner: [{type: Schema.Types.ObjectId, ref: 'User'}],
city: {type: String},
date: {type: Date}
So far so good.
Now lets say you have a route to /user/:id, what you would expect is to get the username and the age, but what if I would also like to return on that route the latest activity?
EDIT: Please note that latest activity isn't a value in the database. it's calculated automatically like activity.find({owner: ObjectId(id)}).sort({date: -1}).limit(1)
What is done right now:
User.findOne({username:req.params.username}).lean().exec(function(err,userDoc)
{
if(err) return errHandler(err);
Activity.findOne({owner:userDoc.username}).sort({date:-1}).exec(function(err,EventDoc){
if(err) return errHandler(err);
userDoc.latest_activity = EventDoc._id;
res.json(userDoc);
res.end();
})
})
The problem with the snippet above is that it is hard to maintain,
What if we want to add more to this API functionality? We would end in a callback of hell of queries unless we implement Q.
We tried to look at Virtual but the issue with that is that you can't
really query inside a mongoose Virtual, since it returns a
race-condition, and you are most likely not get that document on time.
We also tried to look at populate, but we couldn't make it since the documentation on populate is super poor.
QUESTION:
Is there anyway making this more modular?
Is there any way avoiding the DB Query Callback of Hell?
For example is this sort of thing possible?
User.findOne({username:req.params.username}).lean().populate(
{path:'Event',sort:{Date:-1}, limit(1)}
).exec(function(req,res))...
Thanks!
In this case, the best way to handle it would be to add a post save hook to your Activity schema to store the most recent _id in the latest_activity path of your User schema. That way you'd always have access to the id without having to do the extra query.
ActivitySchema.post('save', function(doc) {
UserSchema.findOne({username: doc.owner}).exec(function(err, user){
if (err)
console.log(err); //do something with the error
else if (user) {
user.latest_activity = doc._id;
user.save(function(err) {
if (err)
console.log(err); //do something with the error
});
}
});
});
Inspired by #BrianShambien's answer you could go with the post save, but instead of just storing the _id on the user you store a sub doc of only the last activity. Then when you grab that user it has the last activity right there.
User Model
username : {type: String, unique: true},
age : {type: Number},
last_activity: ActivitySchema
Then you do a post save hook on your ActivitySchema
ActivitySchema.post('save', function(doc) {
UserSchema.findOne({username: doc.owner}).exec(function(err, user){
if (err) errHandler(err);
user.last_activity = doc;
user.save(function(err) {
if (err) errHandler(err);
});
});
});
**********UPDATE************
This is to include the update to the user if they are not an owner, but a particpant of the the activity.
ActivitySchema.post('save', function(doc) {
findAndUpdateUser(doc.owner, doc);
if (doc.participants) {
for (var i in doc.participants) {
findAndUpdateUser(doc.participants[i], doc);
}
}
});
var findAndUpdateUser = function (username, doc) {
UserSchema.findOne({username: username}).exec(function (err, user) {
if (err) errHandler(err);
user.last_activity = doc;
user.save(function (err) {
if (err) errHandler(err);
});
});
});

Resources