updating embedded sub documents - mongoose - node.js

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.

Related

Mongoose can't search by number field

I have a schema that has an id field that is set to a string. When I use collection.find({id: somenumber}) it returns nothing.
I've tried casting somenumber to a string and to a number. I've tried sending somenumber through as a regex. I've tried putting id in quotes and bare... I have no idea what's going on. Any help and input would be appreciated.
Toys.js
var Schema = mongoose.Schema;
var toySchema = new Schema( {
id: {type: String, required: true, unique: true},
name: {type: String, required: true},
price: Number
} );
My index.js is as such
app.use('/findToy', (req, res) => {
let query = {};
if (req.query.id)
query.id = req.query.id;
console.log(query);
// I've tried using the query variable and explicitly stating the object as below. Neither works.
Toy.find({id: '123'}, (err, toy) => {
if (!err) {
console.log("i'm right here, no errors and nothing in the query");
res.json(toy);
}
else {
console.log(err);
res.json({})
}
})
I know that there is a Toy in my mongoDB instance with id: '123'. If I do Toy.find() it returns:
[{"_id":"5bb7d8e4a620efb05cb407d2","id":"123","name":"Dog chew toy","price":10.99},
{"_id":"5bb7d8f7a620efb05cb407d3","id":"456","name":"Dog pillow","price":25.99}]
I'm at a complete loss, really.
This is what you're looking for. Visit the link for references, but here's a little snippet.
For the sake of this example, let's have a static id, even though Mongo creates a dynamic one [ _id ]. Maybe that what is the problem here. If you already a record in your DB with that id, there's no need for adding it manually, especially not the already existing one. Anyways, Drop your DB collection, and try out this simple example:
// Search by ObjectId
const id = "123";
ToyModel.findById(id, (err, user) => {
if(err) {
// Handle your error here
} else {
// If that 'toy' was found do whatever you want with it :)
}
});
Also, a very similar API is findOne.
ToyModel.findOne({_id: id}, function (err, toy) { ... });

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

nodejs app mongoose database where clause with join

I have a schema article defined as:
var ArticleSchema = new Schema({
title: String,
content: String,
creator: {
type: Schema.ObjectId,
ref: 'User'
}
})
And user schema:
var UserSchema = new Schema({
type: String, //editor, admin, normal
username: String,
password: String,
})
I need to query all the article created by editor, i.e. in sql language
select Article.title as title, Article.content as content
from Article inner join User
on Article.creator = User._id
where User.type = 'editor'
This is what I have tried
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'creator.type': creatorType}
Article.find(criteria).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
}
The returned articles is an empty array []
I also tried Article.populate('creator').find(criteria), also not working with error:
utils.populate: invalid path. Expected string. Got typeof `undefined`
There is no concept of joins in MongoDB, as it is a not a relational database.
The populate method is actually a feature of Mongoose and internally uses multiple queries to replace the referred field.
This will have to be done using a multi-part query, first on the User collection, then on the Article collection.
exports.listArticle = function(req, res, next) {
var creatorType = req.query.creatorType
var criteria = {}
if (creatorType)
criteria = {'type': creatorType}
User.distinct('_id', criteria, function (err, userIds) {
if (err) return next(err);
Article.find({creator: {$in: userIds}}).populate('creator').exec(function(err, articles) {
if (err)
return next(err)
//ok to send the array of mongoose model, will be stringified, each toJSON is called
return res.json(articles)
})
})
}

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

How to update document in mongo with nested schema

I have the following code:
add_new_patient : function(username,pname,pid,pdesc,callback){
var find_md = function(){
return function(err,pat){
if (err){
console.log('Error at adding patient: error at searching for users');
callback(2);//In the callback method I'm passing I have specific behavior for error handling for error codes.
return;
}
if (pat.lenth > 0){
console.log('searching for md');
MD.find({'mdname':username},add_patient(pat));
}else{
console.log('Error at adding patient: no user found');
callback(-1);
return;
}
}}
var add_patient = function(pat){
return function(err,md){
if (err){
console.log('Error at adding patient: cannot find md');
callback(-1);
return;
}
callback(0);
}
}
console.log('searching for user '+pid);
User.find({'name':pid},find_md());
}
And these are my schemas:
var mdSchema = mongoose.Schema({
mdname : String,
pacients : [userSchema.ObjectId]
});
var userSchema =mongoose.Schema({
name : String,
password : String,
phone : String,
history : [{timestamp: Date , heart: Number }],
md : {mdname: String, contact: Number}
});
As you can guess from the code I want to add patients to the dms. First I search for the pid in the database. If I find a patient I start to look for mds. When I find the md I want to add the patient to the md. Now I don't know how to add them. The schema shows that I have an array of schemaUser, which is the type of patient, but I don't know how to append to it, not how to create an MD model from object from the data I received from the query. Also what should I insert into the array of patients? The _id of the found patient or the whole object?
I managed to solve it in the following way:
var add_patient = function(pat){
return function(err,md){
if (err){
console.log('Error at adding patient: cannot find md');
callback(-1);
return;
}
var query = {mdname: md.mdname};
console.log(md);
var doThis = { $addToSet: { patients: pat._id } };
console.log(query);
MD.update(query,doThis,done_adding());
}
}
var done_adding = function(){
return function(err,dat){
if (err){
console.log('Error at the end of adding new patient!');
callback(-1);
return;
}
console.log('new patient added');
callback(0);
}
So what this does is: when I have the md to whom I want to add a patient/user I use the update method, with the $addToSet operation so I will have a set of patients associated with an md. I don't know why, but the same code did not work for me with the $push parameter. Then simply nothing happened and when I set the upsert option to true my whole record in the database was overwritten by the id.

Resources