The mongoose findOneAndUpdate function doesn't seem to be working - 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 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);
});
}
}
);

Related

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

MongoDB: how to insert a sub-document?

I am using sub-documents in my MEAN project, to handle orders and items per order.
These are my (simplified) schemas:
var itemPerOrderSchema = new mongoose.Schema({
itemId: String,
count: Number
});
var OrderSchema = new mongoose.Schema({
customerId: String,
date: String,
items: [ itemPerOrderSchema ]
});
To insert items in itemPerOrderSchema array I currently do:
var orderId = '123';
var item = { itemId: 'xyz', itemsCount: 7 };
Order.findOne({ id: orderId }, function(err, order) {
order.items.push(item);
order.save();
});
The problem is that I obviously want one item per itemId, and this way I obtain many sub-documents per item...
One solution could be to loop through all order.items, but this is not optimal, of course (order.items could me many...).
The same problem could arise when querying order.items...
The question is: how do I insert items in itemPerOrderSchema array without having to loop through all items already inserted on the order?
If you can use an object instead of array for items, maybe you can change your schema a bit for a single-query update.
Something like this:
{
customerId: 123,
items: {
xyz: 14,
ds2: 7
}
}
So, each itemId is a key in an object, not an element of the array.
let OrderSchema = new mongoose.Schema({
customerId: String,
date: String,
items: mongoose.Schema.Types.Mixed
});
Then updating your order is super simple. Let's say you want to add 3 of items number 'xyz' to customer 123.
db.orders.update({
customerId: 123
},
{
$inc: {
'items.xyz': 3
}
},
{
upsert: true
});
Passing upsert here to create the order even if the customer doesn't have an entry.
The downsides of this:
it is that if you use aggregation framework, it is either impossible to iterate over your items, or if you have a limited, known set of itemIds, then very verbose. You could solve that one with mapReduce, which can be a little slower, depending on how many of them you have there, so YMMB.
you do not have a clean items array on the client. You could fix that with either client extracting this info (a simple let items = Object.keys(order.items).map(key => ({ key: order.items[key] })); or with a mongoose virtual field or schema.path(), but this is probably another question, already answered.
First of all, you probably need to add orderId to your itemPerOrderSchema because the combination of orderId and itemId will make the record unique.
Assuming that orderId is added to the itemPerOrderSchema, I would suggest the following implementation:
function addItemToOrder(orderId, newItem, callback) {
Order.findOne({ id: orderId }, function(err, order) {
if (err) {
return callback(err);
}
ItemPerOrder.findOne({ orderId: orderId, itemId: newItem.itemId }, function(err, existingItem) {
if (err) {
return callback(err);
}
if (!existingItem) {
// there is no such item for this order yet, adding a new one
order.items.push(newItem);
order.save(function(err) {
return callback(err);
});
}
// there is already item with itemId for this order, updating itemsCount
itemPerOrder.update(
{ id: existingItem.id },
{ $inc: { itemsCount: newItem.itemsCount }}, function(err) {
return callback(err);
}
);
});
});
}
addItemToOrder('123', { itemId: ‘1’, itemsCount: 7 }, function(err) {
if (err) {
console.log("Error", err);
}
console.log("Item successfully added to order");
});
Hope this may help.

Mongoose query to keep only 50 documents

How do I make a query in mongoose to find if a user has 50 documents then remove the oldest one and add in the new one, if not just add in the new one?
This is my attempt:
Notifications.findOne({_id: userId}, function(err, results) {
if(err) throw err;
if(results.length < 50) {
saveNotification();
} else {
Notifications.findByIdAndUpdate(userId, {pull: //WHAT GOES HERE),
function(err, newNotify) {
if(error) throw error;
saveNotification();
});
}
});
function saveNotification() {
var new_notification = new Notification ({
notifyToUserId: creatorId,
notifyFromUserId: null,
notifyMsg: newmsg,
dateNotified: dateLastPosted
});
new_notification.save(function(err, results){
if(!err) {
console.log('notification has been added to the db');
cb(null, resultObject);
} else {
console.log("Error creating notification " + err);
}
});
}
As #Pio mentioned I don't think you can do it in one query with your current schema. But if you have chance to change the schema, you can use fixed size array pattern that is described in the following article Limit Number of Elements in an Array after an Update
Basically you can keep the notifications of users in one document. Key of the document will be userId, and notifications will be stored in an array. Then the following query would achieve your goal.
Notifications.update(
{ _id: userId },
{
$push: {
notifications: {
$each: [ notificationObject ], // insert your new notification
$sort: { dateNotified: 1 }, // sort by insertion date
$slice: -50 // retrieve the last 50 notifications.
}
}
}
)
I am not sure you can do it in one query, but you can
.count({user: yourUser'}) then depending on the count .insert(newDocument) or update the oldest one so you won't remove + insert.
Capped collections do what you want by nature. If you define a capped collection with size 50 it will only keep 50 documents and will overwrite old data when you insert more.
check
http://mongoosejs.com/docs/guide.html#capped
http://docs.mongodb.org/manual/core/capped-collections/
new Schema({..}, { capped: { size: 50, max: 50, autoIndexId: true } });
Remember that when working with capped collection you can only make inplace updates. Updating whole document may change the size of collection that will remove other documents.
I ended up using cubbuk's answer and expanding it to add a notification if there is no array to start with along with upsert...
Notification.findOneAndUpdate({notifyToUserId: to}, {
$push: {
notifyArray: {$each: [newNotificationObject], // insert your new notification
$sort: { dateNotified: 1 }, // sort by insertion date
$slice: -50 // retrieve the last 50 notifications.
}
}
}, {upsert: true}, function(err, resultOfFound) {
if(err) throw err;
if(resultOfFound == null) {
var new_notification = new Notification ({
notifyToUserId: to,
notifyArray: [newNotificationObject]
});
new_notification.save(function(err, results){
if(!err) {
console.log('notification has been added to the db');
cb(null, resultObject);
} else {
console.log("Error creating notification " + err);
}
});
} else {
cb(null, resultObject);
}
});

How can I speed up a mongoDB (mongoose) batch insert with nodejs?

I have a bunch of documents in a collection I need to copy and insert into the collection, changing only the parent_id on all of them. This is taking a very very long time and maxing out my CPU. This is the current implementation I have. I only need to change the parent_id on all the documents.
// find all the documents that need to be copied
models.States.find({parent_id: id, id: { $in: progress} }).exec(function (err, states) {
if (err) {
console.log(err);
throw err;
}
var insert_arr = [];
// copy every document into an array
for (var i = 0; i < states.length; i++) {
// copy with the new id
insert_arr.push({
parent_id: new_parent_id,
id: states[i].id,
// data is a pretty big object
data: states[i].data,
})
}
// batch insert
models.States.create(insert_arr, function (err) {
if (err) {
console.log(err);
throw err;
}
});
});
Here is the schema I am using
var states_schema = new Schema({
id : { type: Number, required: true },
parent_id : { type: Number, required: true },
data : { type: Schema.Types.Mixed, required: true }
});
There must be a better way to do this that I just cannot seem to come up with. Any suggestions are more than welcome! Thanks.
In such a case there is no point to do this on application layer. Just do this in database.
db.States.find({parent_id: id, id: { $in: progress} }).forEach(function(doc){
delete doc._id;
doc.parentId = 'newParentID';
db.States.insert(doc);
})
If you really need to do this in mongoose, I see the following problem:
your return all the documents that matches your criteria, then you iterate though them and copy them into another array (modifying them), then you iterate through modified elements and copy them back. So this is at least 3 times longer then what I am doing.
P.S. If you need to save to different collection, you should change db.States.insert(doc) to db.anotherColl.insert(doc)
P.S.2 If you can not do this from the shell, I hope you can find a way to insert my query into mongoose.

Upserting a document with MongoDB, incrementing a field and setting it to 0 if not existent

I'm using MongoDB in node.js
What I would like is to upsert a document in a collection. The document has an unique ID, a lastAccess field, which stores the date of the last time accessed, and a timesAccessed field, which should be set to 0 on document creation and incremented by 1 if updating.
I tried:
// coll is a valid collection
coll.update(
{user: accountInfo.uid},
{user: accountInfo.uid,
lastAccess: new Date(),
$inc: {timesAccessed: 1},
$setOnInsert: {timesAccessed: 0}
},
{upsert: true, w: 1},
function(err, result) {
if (err) throw err;
console.log("Record upserted as " + result);
});
but node says:
MongoError: Modifiers and non-modifiers cannot be mixed
What is a coincise and safe way to do this?
You should either $set the values or update/replace the whole object. So either update(find_query, completely_new_object_without_modifiers, ...) or update(find_query, object_with_modifiers, ...)
Plus, you cannot $set and $setOnInsert with the same field name, so you will start counting from 1 :) Oh, and you don't need to add the find_query items to the update_query, they will be added automatically.
Try:
col1.update( {
user: accountInfo.uid
}, {
$set: {
lastAccess: new Date()
}
$inc: {
timesAccessed: 1
}
}, {
upsert: true,
w: 1
}, function(err, result) {
if(err) {
throw err;
}
console.log("Record upsert as", result);
});

Resources