Mongoose findOneAndUpdate nested documents - node.js

I'm trying to update a document which contains 2 other documents. To achieve this task, I use findOneAndUpdate, but mongoose throws me a CastError (Cast to ObjectId failed).
My models:
user.js
var userSchema = new Schema({
email: String,
info: {
type : Schema.ObjectId,
required : true,
ref : 'Info'
}
});
module.exports = mongoose.model('User', userSchema);
info.js
var infoSchema = new Schema({
firstname: String,
lastname: String
});
module.exports = mongoose.model('Info', infoSchema);
My query:
var tmp = {
email: req.body.email,
info: {
name: req.body.info.name
}
};
User.findOneAndUpdate({
_id: req.params.id
}, tmp, {
upsert: false,
new: true
}).exec(function(err, doc) {
/* check errors */
/* send response */
});
What am I doing wrong? Are my models poorly set?

CastError: Cast to ObjectId failed
The type of info is Schema.ObjectId, whereas one object
info: {
name: req.body.info.name
}
is passed into it as ObjectId, as result, it failed.
Here maybe one work around.
var new_info = new Info({
firstname: req.body.info.name.firstname, //
lastname: req.body.info.name.lastname //
});
new_info.save(function(err, doc) {
if (err)
console.log(err);
else {
var tmp = {
email: req.body.email,
info: doc._id
}
User.findOneAndUpdate({
_id: req.params.id
}, tmp, {
upsert: false,
new: true
}).exec(function(err, doc) {
//
});
}
});

Related

How to use mongoose to create a student that's managed by a parent

Edit #2: Working code
This does what I want it to do now:
// route
app.post("/users/:id/createStudent", function(req, res){
Student.create(function(err){
if(err){
console.log(err)
res.redirect("/")
} else {
const newStudent = new Student({
firstName: req.body.firstName,
lastName: req.body.lastName,
age: req.body.age,
instrument: req.body.instrument,
});
newStudent.save()
.then(() => Parent.findById(req.params.id))
.then((parent) => {
parent.students.push(newStudent);
return parent.save();
});
req.flash("success", "Successfully Created Student");
res.redirect("/users/:id");
}
})
});
// parent model
const mongoose = require("mongoose"),
Schema = mongoose.Schema,
passportLocalMongoose = require("passport-local-mongoose");
const ParentSchema= new Schema({
username: String,
lastName: String,
email: String,
password: String,
students: [],
});
ParentSchema.plugin(passportLocalMongoose);
const Parent = mongoose.model("parent", ParentSchema);
module.exports = Parent;
Brand new developer here. I'm trying to build an app where a parent user can create a student user and a teacher user can assign said student weekly tasks. I'm having trouble with the create student route. I'm not sure how to get the student object to store the parent's id properly. I've been trying to find a solution for a few days now and can't seem to find anything that deals with this specifically and the docs haven't helped either. I hope I'm just missing something and one of you kind folks can at least point me in the right direction.
Here's my code:
//parent model
const mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
const parentSchema= new mongoose.Schema({
username: String,
lastName: String,
email: String,
password: String,
student: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Student"
},
username: String,
}
});
parentSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Parent", parentSchema);
// student model
const mongoose = require("mongoose");
const studentSchema = new mongoose.Schema({
firstName: String,
lastName: String,
age: String,
instrument: String,
parent: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Parent"
},
username: String
},
});
module.exports = mongoose.model("Student", studentSchema);
// create student route
app.post("/users/:id/createStudent", function(req, res){
const newStudent = new Student({
firstName: req.body.firstName,
lastName: req.body.lastName,
age: req.body.age,
instrument: req.body.instrument,
parent: {
_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Parent"
},
username: String
},
});
Parent.findById(req.params.id, function(err, foundParent){
if(err){
req.flash("error", "Something went wrong");
console.log(err);
res.redirect("/users/:id");
} else {
const parent = foundParent
Student.create(newStudent, parent, function(err, student){
if(err){
console.log(err);
} else {
student.parent.id = req.parent._id;
student.parent.username = req.parent.username;
student.save();
parent.student.push(newStudent);
parent.save();
req.flash("success", "Successfully Created Comment");
res.redirect("/users/" + parent._id);
}
})
}
});
});
In the mongo shell, db.students.find() puts out this as my student object after I fill out the form:
db.students.find()
{ "_id" : ObjectId("5f0b33facd6fa70355f14774"), "firstName" : "Johnny", "lastName" : "Apple", "age" : "2014-03-04", "instrument" : "drums", "parent" : { "username" : "function String() { [native code] }" }, "__v" : 0 }
Sorry for the long code. I've just been trying so many things that it's added up to what you see here. Thanks for any advice you might give and for reading this far.
Edit
Updated code:
app.post("/users/:id/createStudent", function(req, res){
Parent.findById(req.params.id, function(err, foundParent){
if(err){
req.flash("error", "Something went wrong");
console.log(err);
res.redirect("/users/:id");
} else {
const newStudent = new Student({
firstName: req.body.firstName,
lastName: req.body.lastName,
age: req.body.age,
instrument: req.body.instrument,
parent: {
id: foundParent,
username: foundParent.username
},
});
Student.create(newStudent, foundParent, function(err, student){
if(err){
console.log(err);
} else {
student.parent._id = foundParent._id;
student.parent.username = foundParent.username;
student.save();
foundParent.student.push(newStudent);
foundParent.save();
req.flash("success", "Successfully Created Student");
res.redirect("/users/" + foundParent._id);
}
})
}
});
});
So the new error this is producing is DocumentNotFoundError: No document found for query "{ _id: 5f0a7026e330500413ee599a }" on model "Student" which I assume is because I don't have populate in there. I'm just not sure where the populate option would fit in?
mongoose.Schema.Types.ObjectId is a type not a value.
Try using mongoose.Schema.Types.ObjectId(req.parent.id)
Edit
Another option to try:
Move the const newStudent = block inside the else block of the findById callback.
Inside the new Student( constructor, use
parent: {
id: foundParent,
username: foundParent.username
}
Because of the schema definition, when you save the document, mongoose should store a DBRef in the student collection, which can then be reconstituted with populate.

Getting error about $pushAll when going to endpoint, but not using it anywhere

I have a strange one...
I've developed an api with Node/Express/Mongoose using Mongodb 3.4.9, now it's 3.4.17.
I have no ideal why, but for some reason a block of code I have been using for ages is throwing an error:
{name: "MongoError", message: "Unknown modifier: $pushAll", driver: true, index: 0, code: 9,…}
code: 9
driver: true
errmsg: "Unknown modifier: $pushAll"
index: 0
message: "Unknown modifier: $pushAll"
name: "MongoError"
Here is the code:
router.route('/addemail/:id')
// ADD EMAILS
.put(function(req, res){
Profile.findOne({'owner_id':req.params.id}, function(err, profile){
if(err)
res.send(err);
profile.emails.push({
email_type: req.body.email_type,
email_address: req.body.email_address
})
profile.save(function(err){
if(err)
res.send(err);
res.json(profile);
});
});
});
As you can see, I'm not using $pushAll in this block of code, or actually anywhere in my code.
What else could be causing this???
Thanks for any guru advise.
Update: Here is my model for the profile and I'm including the emails model next:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// SUBDOCUMENTS
var AddressesSchema = require('./profile/addresses');
var BusinessesSchema = require('./profile/businesses');
var EmailsSchema = require('./profile/emails');
var PhonesSchema = require('./profile/phones');
var SocialSchema = require('./profile/social');
// PROFILE (PARENT) MODEL
var ProfileSchema = new Schema({
//PROFILE INFO
owner_id: {
type: String,
require: true,
unique: true
},
notice: {
type: Number, // 1=profile, 2=profile and cards
},
first_name:{
type: String
},
last_name:{
type: String
},
initial:{
type: String
},
birthday:{
type: Date
},
highschool:{
type: String
},
college:{
type: String
},
facebook:{
type: String
},
linkedin:{
type: String
},
linkedin_bus:{
type: String
},
twitter: {
type: String
},
google: {
type: String
},
pinterest: {
type: String
},
user_image: {
type: String
},
contacts:[{
type:Schema.Types.ObjectId,
ref:'Contact'
}],
//SUBDOCUMENTS
emails:[EmailsSchema],
phones:[PhonesSchema],
addresses:[AddressesSchema],
businesses:[BusinessesSchema],
social:[SocialSchema]
});
module.exports = mongoose.model('Profile', ProfileSchema);
Here is what the emails model looks like:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// CONTACT (PARENT) MODEL
var EmailSchema = new Schema({
//CONTACT INFO
email: {
type: String,
require: true
},
date_registered: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Email', EmailSchema);
Mongoose probably creates a $pushAll under the hood which, however, has been removed in newer version of MongoDB as you can see here. So this is why you get the error.
I suggest you upgrade to the latest version of Mongoose which will fix this.
Also see these discussions on the Mongoose repo: https://github.com/Automattic/mongoose/issues/4455
https://github.com/Automattic/mongoose/issues/5574
Pardon me for asking but why don't you just:
// ADD EMAILS
.put(function(req, res) {
Profile.update({'owner_id': req.params.id},
{
$addToSet: {
email_type: req.body.email_type,
email_address: req.body.email_address
}
});
});
It seems you just want to add an object to an array in a mongo document based on owner_id. $addToSet does that.
You should get advantage of some mongodb nice features i.e. you could do these:
Profile.findOneAndUpdate({'owner_id':req.params.id},{addToSet:{emails:[ email_type: req.body.email_type, email_address: req.body.email_address]}}, function(err, profile){
if(err){
res.send(err);
} else {
res.json(profile);
}
}

Populate in Mongoose not working

I am trying to perform associations by referencing method. There are 2 models:
1. User
2. Product
I have established one-to-one relationship of 1 user can have multiple products. User creation is successful
Product creation is successful
Code Inputs
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/product_demo_x9");
Product Schema
var productSchema = new mongoose.Schema({
category : String,
Brand: String
});
var Product = mongoose.model("product", productSchema);
User Schema
var userSchema = new mongoose.Schema({
email: String,
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Product"
}
]
});`
var User = mongoose.model("user", userSchema);
User Creation
User.create({
email: "madhur#google.com",
name: "Maddy"
},function(err,newUser){
if(err){
console.log(err);
}
else {
console.log(newUser);
}
});
Product Creation
Product.create({
category: "Smartwatches",
Brand: "Samsung and Google"
},
function(err,product){
console.log(product);
User.findOne({name : "Maddy"},function(err,foundUser){
if(err) {
console.log(err);
}
else {
foundUser.products.push(product);
foundUser.save(function(err,updatedUser){
if(err){
console.log(err);
}
else {
console.log(updatedUser);
}
});
}
});
});
Display of associated Data on the console
User.find({email: "madhur#google.com"}).
populate("products").
exec(function(err,user){
if(err){
console.log(err);
}
else {
console.log(user);
}
});
Code Outputs
User Creation (Success)
[{
products: [],
_id: 5a47acb0317d4e3c2081b8ce,
email: 'madhur#google.com',
name: 'Maddy',
__v: 0
}]
Product Creation and associating (Success)
{
_id: 5a47acd53c771123b4018ff1,
category: 'Smartwatches_2',
Brand: 'Samsung and Google',
__v: 0
}
{
products: [ 5a47acd53c771123b4018ff1 ],
_id: 5a47acb0317d4e3c2081b8ce,
email: 'madhur#google.com',
name: 'Maddy',
__v: 1
}
Display of embedded data using populate - Failure!!
{ MissingSchemaError: Schema hasn't been registered for model "products".
Use mongoose.model(name, schema)
at new MissingSchemaError
Can anyone please explain me how to do it correctly?
Thanks in Advance
Model Name is Case-sensitive
'Product' is not equal to 'product'
and when u create a model as 'product' (singular) it converts it into plural, i.e. 'products', this is default mongoose behavior, can be overridden.
so change the following:
var userSchema = new mongoose.Schema({
email: String,
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "products" //<---- changed 'Product' to 'products'
}
]
});`
var User = mongoose.model("user", userSchema);
Try this
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/product_demo_x9');
var productSchema = new mongoose.Schema({
category: String,
Brand: String
});
var Product = mongoose.model('Product', productSchema);
var userSchema = new mongoose.Schema({
email: String,
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}
]
});
var User = mongoose.model('User', userSchema);
User.create({
email: 'madhur#google.com',
name: 'Maddy'
}, function(err, newUser) {
if (err) {
console.log(err);
} else {
console.log(newUser);
}
});
Product.create({
category: 'Smartwatches',
Brand: 'Samsung and Google'
},
function(err, product) {
console.log(product);
User.findOne({name: 'Maddy'}, function(err, foundUser) {
if (err) {
console.log(err);
} else {
foundUser.products.push(product);
foundUser.save(function(err, updatedUser) {
if (err) {
console.log(err);
} else {
console.log(updatedUser);
}
});
}
});
});
User.find({email: 'madhur#google.com'})
.populate('products')
.exec(function(err, user) {
if (err) {
console.log(err);
} else {
console.log(user);
}
});
Solved
Did the following
Downgraded my Mongoose version from 5.00x to 4.10.8 using the following command npm remove mongoose then npm install mongoose#4.10.8 --save
Made the following change in app.js file
var userSchema = new mongoose.Schema({
email: String,
name: String,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "product" //<---- changed 'Product' to 'product'
}
]
});`
var User = mongoose.model("user", userSchema);
Thanks to the Stack community for giving a try!

Mongoose objectId is not saving in another collection

I am new to mongoose and nodejs. Please help me to understand what is wrong in my code while saving collection. My user.js looks like below
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema ({
name: { type: String, required: true },
username:{ type:String, required: true, index:{ unique: true}},
password:{ type: String, required: true, select: false},
email:{ type: String, required: true, select: true},
mobile:{ type: String, required: true, select: true},
nativecid:{ type: Schema.Types.ObjectId, ref:'City'},
regdate:{ type: Date, default: Date.now },
did:{ type: String, required: false }
});
UserSchema.pre('save', function(next){
var user = this;
if(!user.isModified('password')) return next();
bcrypt.hash(user.password, null, null, function(err, hash){
if(err) return next(err);
user.password = hash;
next();
});
});
UserSchema.methods.comparePassword = function(password){
var user = this;
return bcrypt.compareSync(password, user.password);
};
module.exports = mongoose.model('User', UserSchema);
And my City model ie city.js looks like below
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CitySchema = new Schema({
name: { type: String, required: true },
status: { type: Boolean, default: true },
date: { type: Date, default: Date.now }
});
module.exports = mongoose.model( 'City', CitySchema );
City is already stored in database as below
{ "_id" : ObjectId("56a4a0adb0f445561cfd4e37"), "name" : "New York", "date" : ISODate("2016-01-24T10:00:13.220Z"), "status" : 1, "__v" : 0 }
I want this ObjectId of New York should be saved in user collection while user signups. I am trying like below but not succeed.
var User = require('../models/user');
var City = require('../models/city');
api.post('/signup', function(req, res){
var user = new User({
name: req.body.name,
username: req.body.username,
password: req.body.password,
email: req.body.email,
mobile: req.body.mobile
});
City
.findOne({ name: "New York" })
.populate('_id')
.exec(function (err, city1) {
if(err){
res.send(err);
return;
}
console.log('The creator is %s', city1.name);
return user.nativecid = city1._id;
})
user.save(function(err){
if(err){
res.send(err);
return;
}
res.json({
success: true,
message: 'User created successfully!'
});
});
});
I am not understanding what is wrong. Please advice me.
Just save user in city.findOne (nested) and may be no need to populate you should use projection.
City.findOne({ name: "New York" }, {name:1}) // ID return defaulat
.exec(function (err, city1) {
if(err){
res.send(err);
return;
}
console.log('The creator is %s', city1.name);
user.nativecid = city1._id;
user.save(function(err1){
if(err1){
res.send(err1);
return;
}
res.json({
success: true,
message: 'User created successfully!'
});
});
})

Handle schema references on mongoose

I'm trying to define a simple RESTful API using Node.js, mongoose and restify. The goal is to have users which can comment on profiles of others users. For this I have a comment endpoint that receives a text, the author and the target of the comment (other user).
I want to reference users so I defined next schemas:
User schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
"username": { type: String, unique: true, required: true },
"password": { type: String, required: true },
"comments": [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
});
mongoose.model('User', UserSchema);
Comment schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
date: { type: Date, default: Date.now },
text: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
target: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});
mongoose.model('Comment', CommentSchema);
I also have this controller (just showing createComment function):
exports.createComment = function(req, res, next) {
var authorId, targetId;
User.findOne({ _id: req.params.authorId}, function(err, author) {
if (author) {
User.findOne({ _id: req.params.targetId}, function(err, target) {
if (target) {
var comment = new Comment();
comment.text = req.params.text;
comment.author = author._id;
comment.target = target._id;
comment.save(function(err, comment) {
if (err) {
res.status(500);
res.json({
type: false,
data: 'Error occurred: ' + err
});
} else {
res.json({
type: true,
data: comment
});
}
});
} else {
res.json({
type: false,
data: 'User ' + req.params.authorId + ' not found'
});
}
});
} else {
res.json({
type: false,
data: 'User ' + req.params.targetId + ' not found'
});
}
});
};
So, I have three questions:
Why do I need to check if the user received exists? I would like to receive only the id and store it but I have to do two more queries to check it myself.
What I have to do to store in User only comments where that user is the target? solved in the edited code
How can I simplify this code? Is a pain to have async queries executed in order. I would like to have generic errors and not to have to handle each one.
EDIT: I've simplified the code using validations on the schema:
Comment schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = mongoose.model('User');
var CommentSchema = new Schema({
date: { type: Date, default: Date.now },
text: { type: String, required: true },
author: { type: Schema.Types.ObjectId, ref: "User", required: true },
target: { type: Schema.Types.ObjectId, ref: "User", required: true }
});
CommentSchema.path('author').validate(function(value, respond) {
User.findOne({ _id: value}, function(err, user) {
respond(!err && user);
});
}, 'Author doesn\'t exists');
CommentSchema.path('target').validate(function(value, respond) {
User.findOne({ _id: value}, function(err, user) {
respond(!err && user);
});
}, 'Target user doesn\'t exists');
mongoose.model('Comment', CommentSchema);
Controller:
exports.createComment = function(req, res, next) {
var comment = new Comment(req.body);
comment.save(function(err, comment) {
if (err) {
res.status(500);
res.json({
type: false,
data: 'Error occurred: ' + err
});
} else {
User.findOne({ _id: comment.target }, function(err, user) {
user.comments.push(comment);
user.save();
});
res.json({
type: true,
data: comment
});
}
});
};
The problem with this is that now I have to use _id on queries (I would like to use a custom id) and I'm doing three queries every time I want save a comment (2 for validation and one more to store the comment). Is there a better way to to this?
You can use the select option of query in mongo to select only the _id field of the user, like this:
User.findOne({_id:req.params.authorId}).select({_id:1}).exec(function(err,user) {})
After you pull the userTarget from mongo, you need to add the comment._id to his list of comments, and save him:
target.comments.push(comment._id);
target.save(function(err, targetAfterSaved) {})
read about async or q, they are my favorites Libraries to handle with async functions. For handle with errors like you want, you can add some listeners - here is the documentation from restify site.
Hope you understand, if you need any help let me know

Resources