Mongoose not saving document - node.js

I've been working on a simple update function, that looks as follows:
exports.update = function update(req, res, next){
User.findById(req.param('userId'))
.exec(function (err, user) {
if(err) return next(err);
if(!user)
return res.sendData(null, 404, lang.userNotFound);
var info = req.param('info');
// Get the update to be made, in JSON format
if(info !== Object(info)) info = JSON.parse(info);
user.info = setProperties(user.info, info);
// console.log(user) gives the updated user object
user.save(function(err, usr) {
if (err) return next(err);
// console.log(usr) shows the updated user object
res.sendData();
});
});
};
function setProperties(object, update) {
if(update!== Object(update))
object = update;
else {
for (var prop in update) {
if (object.hasOwnProperty(prop))
object[prop] = setProperties(object[prop], update[prop]);
else
object[prop] = update[prop];
}
}
return object;
}
But unfortunately, although everything seems to work, in my Database nothing changes. I do not get any errors. Could anyone shed some light on this mystery? Thanks!
For reference (if it is relevant), here is my Schema:
var UserSchema = new Schema({
createdAt: {type: Date, default: Date.now},
lastActivity: {type: Date, default: Date.now},
info : {type : Schema.Types.Mixed},
});

Ah, I found the problem. Mongo was indeed performing the save, but it didn't know that an attribute had changed.
In the end, fixing it was as simple as adding
user.markModified('info');
And then everything performs as expected. It's a shame that Mongoose does not recognize this for us. Thanks to #vinz243 for pointing me in the right direction!

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

Catch error when using populate with mongoose

I have the next model and route with mongoose:
In my colection I have some invalids id's to "cidade" field and this is why I am getting the error showing below.
The error happens in the line:
.populate('cidade')
Is there a way to execute my router(code is below) in:
router.get('/:id',function(req,res,next){ .....
without stop on that error?
If an invalid "id" is found, I´d just like to ignore it and proceed to next.
My collections are too big and can have some invalids "ids" to "cidade" field.
//error
angular.js:14328 Possibly unhandled rejection: {"data":{"message":"Cast to ObjectId failed for value \"Ararendá\" at path \"_id\" for model \"Cidade\"","name":"CastError","stringValue":"\"Ararendá\"","kind":"ObjectId","value":"Ararendá","path":"_id"},"status":500,"config":
//models and route
//cidade
cidadesSchema = new mongoose.Schema({
uf: {type: String, unique:true},
cidade: {type: String, unique:true}
});
module.exports = mongoose.model('Cidade', cidadesSchema,'cidades' );
//profiss
var profissionaisSchema = new mongoose.Schema({
nome: {type: String, unique:true},
cidade: {type:mongoose.Schema.Types.ObjectId, ref:'Cidade'},
estado: {type:mongoose.Schema.Types.ObjectId, ref:'Estado'},
cep: {type: String},
});
module.exports = mongoose.model('Profissional', profissionaisSchema,'profissionais' );
//route
const callback=function(err,data,res){
if (err) return res.status(500).json(err);
return res.status(200).send(data);
}
router.get('/:id',function(req,res,next){
const query=req.params.id;
Profissional.findById(query).populate('profissao')
.populate('cidade')
.exec( (err,data) => {
callback(err,data,res)
});
});
I don't think you can tell Mongoose to just ignore those errors and keep going, so you're going to have to implement the population yourself (which should be relatively easy because you're using findById which would only yield, at most, one document).
Here's some (untested) code:
Profissional.findById(query).populate('profissao').exec( (err, profi) => {
if (err) {
return res.status(500).json(err);
} else if (! profi || ! /^[a-f0-9]{24}$/i.test(profi.cidade)) {
return res.status(200).send(profi);
}
Cidade.findById(profi.cidade).exec((err, cidade) => {
if (err) {
return res.status(500).json(err);
}
profi.cidade = cidade;
return res.status(200).send(profi);
});
});
If the cidade property looks like a valid ObjectId, it will run a query to retrieve it, otherwise it won't bother.

Accessing properties of object/cursor returned from .find and .forEach in mongodb with nodejs

changed schema and everything went crazy (see changes below). now accessing properties from .find() and cursor.forEach() is returning 'undefined' in backend:
EDIT: have found
.find().lean().exec(callback)
allows access to properties in callback but hard to do anything with them and that to access properties by doing
doc._doc.property
works in callbacks:
.find(function(err,doc){for (i in docs){doc=docs[i]; console.log(doc._doc.property)}}
and .forEach(function(doc){console.log(doc._doc.property)}:
My schema once looked like this
for collection of people
{
name: String,
v: Types.ObjectId, ref: V //shorthand
r: {
e: [{}],
u: [{}]
}
}
now it looks like this
var people = new mongoose.Schema (
{
name: String,
v: {type: mongoose.Schema.Types.ObjectId, ref: V}
r: {
e: [{type: mongoose.Schema.Types.ObjectId, ref: R}],
u: [{type: mongoose.Schema.Types.ObjectId, ref: R}]
}
}
)
mongoose.model('people',people);
for collection of r
var collR = new mongoose.Schema({}, {strict:false})
mongoose.model('R',collR)
nodejs controller 1:
module.exports.getProducts = function (req, res) {
people.find(req.query)
.populate('v r.e r.u')
.exec(function (err, data) {
if (err) {sendJsonResponse(res,400,err)}
else {
data.forEach(function(single){
single.r.e.forEach(function(sing){
console.log(sing) //defined, and i saw rating, and its defined
console.log(sing.rating); //undefined
// do something with sing.rating but it's undefined here
})
})
sendJsonResponse(res,200,data); //not undefined on frontend success callback
}
});
};
node controller 2:
module.exports.getProducts = function (req, res) {
people.find(req.query)
.populate('v r.e r.u')
.exec(function (err, data) {
if (err) {sendJsonResponse(res,400,err)}
else {
data.forEach(function(single){
R.find({person: single.name}, function (err, dat) {
dat.forEach(function(sing){
console.log(sing) //defined and rating defined
console.log(sing.rating); //undefined ugh.
//do something with rating but cant bc undefined here
})
})
})
//if i send data back here, in success callback, data[i].r.e[j].rating is defined for all i and j, whaaa!?!
}
});
};
one of the sing's logged from the cursor.forEach loop---
{_id: 1254357653, name: peep, rating: 6, type: some type}
EDIT:
ya so:
collection.find(query).exec(function(err,docs) {
docs.forEach(function(singleDoc) {
console.log(singleDoc._doc.property); //DEFINED, bad boyz 4 lyfe *_*
})
})
so i finally decided to console.log the darn keys of the document returned from a cursor.forEach
this also returns defined:
collection.find(query).lean().exec(function(err,docs) {
console.log(docs[i].property); //for all i, THEY'RE DEFINED!!!!! wooo
})
well now another issue pops up when i try to do an update inside a find
collection.find(query).exec(function(err,docs) {
if (err) {return errorHandler(err)};
var doc = docs[0];
var captainKeyes = Object.keys(req.body);
for (k = 0 ; k < captainKeyes.length ; k++) {
//update the doc key/value pairs with what is sent in req.body
doc._doc[captainKeyes[k]] = req.body[captainKeyes[k]];
//from above, learned to access properties captainKeyes[k], you have to first access
//the hidden property _doc to get to actual doc
}
doc.save()
//old doc is still in db, damn. and all this used to work before
//we added that R collection :(
})
I changed the schema for the collection R to have some keys, changing it from just an empty object with strict: false.
from {{},strict:false} to {{name: String, rating: Number, person: String},strict:false}
now i dont have to use _doc, wooohoooo, and all the queries works normally again.
moral of the story, i didn't really understand how to implement a schemaless collection properly, and then stuff got cray

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