How do you update a referenced document in mongoose? - node.js

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

Related

The mongoose findOneAndUpdate function doesn't seem to be working

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 check if the number of available rooms in the particular hotel greater than 0 and if it is, update the number of available rooms in the hotel by reducing it by 1.
Here is 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 code I'm having trouble with.
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)
});
}
});
When I try to create a new booking using postman, it creates the new booking but it doesn't check the available rooms option. If a hotel has zero available rooms, it still goes on to create the booking. Also, the number of available rooms doesn't go below 0 at all.
Going through your code, the else clause in the findOneAndUpdate() callback is always executed because your code doesn't produce any error, it just isn't checking if there is a matching document from the update query.
You need to check is there is a matching document as well as if the original document has available rooms for you to create the new booking.
The following code implements the specification:
I want to check if the number of available rooms in the particular hotel greater than 0
{ '_id': hotelId, 'availableRooms': { '$gt': 0 } },
and if it is,
update the number of available rooms in the hotel by reducing it by 1.
{ '$inc': { 'availableRooms': -1 } },
create a new booking
if (hotel && hotel.availableRooms > 0) { /* create booking */ }
Correct update
Hotel.findOneAndUpdate(
{ '_id': hotelId, 'availableRooms': { '$gt': 0 } },
{ '$inc': { 'availableRooms': -1 } },
function (err, hotel) {
if (err) throw err;
else if (hotel && hotel.availableRooms > 0) {
booking.create(req.body, function(err, confirmedBooking){
if (err) throw err;
res.json(confirmedBooking);
});
}
}
);

Trying to update nested / subdocument in Express

I have a Mongoose model that has nested array and a subdocument.
I seem to be ok when posting to the object arrays/subdocument, but I'm having trouble with the .put
I hard coded the params for testing, just in case they were not coming in from PostMan for some reason.
The result I get from the above code is an empty array!
So I'm getting the right record and it creates the "phone" array, but does not populate.
.put(function(req, res){
Member.find({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
member.phone.push({
number: "78787878787",
phoneType: 2
})
member.save(function(err){
if(err)
res.send(err);
res.json(member);
});
});
});
I want to have an endpoint that simply adds another "phone" record.
Here is my model:
//DEPENDENCIES
var mongoose = require('mongoose');
var contactSchema = new mongoose.Schema({
name:{type:String},
age:{type:Number}
});
var phoneSchema = new mongoose.Schema({
number:{ type: String },
phoneType:{ type: Number }
})
var memberSchema = new mongoose.Schema({
firstname: {
type: String
},
lastname: {
type: String
},
phone:[phoneSchema],
contacts:[contactSchema]
});
//RETURN MODEL
module.exports = mongoose.model('member', memberSchema);
Now when I run my code I get the following undefined for "members":
{ id: '587bcbffe64e9f28a6894dd7' }
[ { _id: 587bcbffe64e9f28a6894dd7,
lastname: 'Stanley',
firstname: 'Dave',
__v: 0,
contacts: [ [Object] ],
phone: [] } ]
events.js:154
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'push' of undefined
find returns an array of documents, not just one document. Thats is why it is giving error when you are trying to do member.phone.
Use findOne instead of find as you are querying by _id, it will return only one matched document or null(if its not present), so its a better choice than find.
Also, its better to check if the result is null or not. member will be null if no such _id is present.
Member.findOne({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
else if(member!=null)
{
member.phone.push({
number: "78787878787",
phoneType: 2
});
member.save(function(err){...});
}
});
If you are keen on using find. Use member[0] (first element) instead of member.
Member.find({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
else if(member.length!=0)
{
member[0].phone.push({
number: "78787878787",
phoneType: 2
});
member[0].save(function(err){...});
}
});
Hope that helps you.

Issues with mongodb/nodejs/express/mongojs and findAndModify

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

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);
});
});
});

updating embedded sub documents - mongoose

I have the following schemas:
var reviewSchema = new mongoose.Schema({
comments : String,
rating : String,
submitted_date: {type: Date, default: Date.now},
numAgreed : Number,
numDisagreed : Number
});
var userSchema = new mongoose.Schema({
firstName : String,
lastName : String,
numRatings : Number,
averageRating: Number,
reviews : [reviewSchema]
});
I am implementing an agree function (increment number of those who agreed with the review) for every review as follows:
exports.processAgree = function(req,res){
var firstName = req.body.firstName;
var lastName = req.body.lastName;
var index = req.body.index;
User.findOne({firstName:firstName,lastName:lastName}).lean().exec(function(err,user) {
if (err) {
throw err;
}
else{
user.reviews[index].numAgreed++;
user.markModified('reviews');
user.save(function (err) {
if (err) throw err;
});
}
});
};
However, I get the error:
reviewedUser.markModified('reviews');
^
TypeError: Object #<Object> has no method 'markModified'
I searched through stackoveflow and have seen responses to this issue but they don't to work in my case. E.g. There was a response at How to update an embedded document within an embedded document in mongoose?
The solution suggests to declare child schemas before the parent schemas which is the case in my situation.
Please let me know if more information is required to help.
Thanks.
As Johnny said, you should remove the call to lean method on Query.
Such that your code would look like
User.findOne({firstName:firstName,lastName:lastName}).exec(function(err,user) {
if (err) {
throw err;
}
else{
user.reviews[index].numAgreed++;
user.markModified('reviews');
user.save(function (err) {
if (err) throw err;
});
}
});
Lean is used to strip all the service methods and properties from objects that come from Mongoose methods. If you don't use lean method on Query object, Mongoose will return instances of Model. Mongoose doc on lean().
And markModified method, that you are looking for, resides in Mongoose Model instance. By the way, save is in Model instance too.

Resources