_id not being generated for subdocs when It's supposed to - node.js

Here are my two schemas
var reviews = new Schema({
scenarioId: { type: Schema.Types.ObjectId, required: true},
authorId: { type: Schema.Types.ObjectId , required:true },
reviewNote: String,
subReviews: [subReviewSchema],
active: {type: Boolean, default: true},
display: {type: Boolean, default: true},
date : {type: Date, default: Date.now()}
});
and the subscheA for subreviews
var subReviews = new Schema({
authorId: { type: Schema.Types.ObjectId, required:true },
subReviewNote: String,
date: {type: Date, default: Date.now()},
active: {type: Boolean, default: true},
display: {type: Boolean, default: true}
});
and here is my code that updates the document
exports.addSubReview = function (req, res) {
var id = req.params.id;
var update = req.body;//scenario Id
Review.findById(id, function (err, obj) {
if (err || !obj) { return res.send(404, { error: err.message }); }
obj.subReviews.push(update);
obj.save(function (err) {
if (err) { return res.send(404, { error: err.message }); }
return res.send(200, obj);
});
});
};
For some reason though whenever I send an http post to this code the results only adds what i send in the post request not _id _v or any other things that I would expect mongoose/mongodb to add as boilerplate. Here is an example document in my database
{
"__v": 2,
"_id": "531e3214a30f5f8427830a97",
"authorId": "52fd0e6df8352c184b000004",
"reviewNote": "aaaaaaaaaaaaaaaaa",
"scenarioId": "531a5b80af15cffc051cea67",
"date": "2014-03-10T21:37:05.230Z",
"display": true,
"active": true,
"subReviews": [
{
"subReviewNote": "This is a subReview",
"authorId": "52fd0e6df8352c184b000004"
},
{
"subReviewNote": "This is a subReview",
"authorId": "52fd0e6df8352c184b000004"
}
]
}
Any ideas on why _id is not being added to my subDocs in subReviews?.

My guess is that the problem is in the parent doc where you say:
subReviews: [subReviewSchema]
But you named the child schema variable
subReviews
Not subReviewSchema. But that's a guess, from what you've posted. I'd have to see the code together to get a better picture, unless this is it.
But this would explain it, since subReviews: is just expecting an Object because of this naming issue - and an object is exactly what it gets when you POST,so it just pushes it into the array as it expects.
EDIT
I poked around in the mongoose code on github and I am less confident in my answer above, although I guess it could still be possible. However, I did stumble upon this comment, when declaring schemas:
When nesting schemas, (children in the example above), always declare the child schema first before passing it into is parent.
I have less confidence in my original answer, because it looks like if you named the variable incorrectly, mongoose is going to throw a TypeError

Related

It is possible to pull elements from a referred objects' array using mongoose?

I have 2 mongo schemas related one with the other using ObjectId:
var User = new Schema({
username: {
type:String,
unique: true
},
password: {
type:String
},
verified: {
type: Boolean,
default: false
},
lastActivity:{
type:Date,
default:Date.now
}
});
And a watitingRoom schema with lists all the users:
var WaitingRoom = new Schema({
lastActivity:{
type:Date,
default:Date.now
},
clients: [{
type : mongoose.Schema.ObjectId,
ref: 'User'
}],
videocalls: [{
type: mongoose.Schema.ObjectId,
ref:'VideoCall'
}]
});
So, I want to 'refresh' my clients array pulling all the clients which a lastActivity less than the current time. I tried it by using the $pull tool present in mongoose. After googling and mixing different examples I tried things like:
WaitingRoom.findOneAndUpdate({}, { lastActivity: new Date(),
$pull : {clients : {"clients.lastActivity": { $lt: new Date() }}}
}, options)
.populate("clients")
.exec( function(error, waitingRoom) {
if (err) { return res.status(500).send({ msg: err.message }); }
})
Which finds the unique waiting room, updates the lastActivity field and tries to pull all the clients that has a clients.lastActivity less than the current date.
(Obviously this snipped code doesn't work)
The problem is that I didn't find any documentation or example that explains if it is possible to pull elements from a referred ObjectId schema using a nested condition clients.lastActivity
You need to first find the ids from User database and then need to $pull them from the WaitingRoom database
User.find({ lastActivity: new Date() }).then((users) => {
const ids = []
users.map((user) => {
ids.push(user._id)
})
WaitingRoom.update({}, { $pull: { clients: ids }}, { multi: true }).then(() => {
console.log('removed')
})
})

Need some clarification on mongoose/mongodb populate command

Hello so I am making a basic app with users and posts.
I followed the mongoose documentation on population (http://mongoosejs.com/docs/2.7.x/docs/populate.html) and setup my Schemas so that the users and be connected to posts
var userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: String,
created_at: Date,
updated_at: Date,
admin: Boolean,
posts: [{ type: mongoose.Schema.ObjectId, ref: 'Post' }]
});
var postSchema = new mongoose.Schema({
_user : [{ type: mongoose.Schema.ObjectId, ref: 'User' }],
audioFile: { type: String, required: true },
imageFile: { type: String },
title: { type: String, required: true },
artist: { type: String, required: true },
start: { type: String, required: true },
stop: { type: String, required: true },
genre: { type: String, required: true },
tags: [{ type: String }]
});
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
res.render('main.njk', {
posts : allPosts,
title : 'Title',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
Thats all fine and gravy and I can run a foreach loop on allPosts to pull each one in my HTML, but when I try to think of how I am going to display all the posts with their respective users attached to each post I am unsure of how to connect the two since all the examples in the mongoose doc is just mainly for findOne.
I was thinking something like this
app.get('/', function (req, res){
Post.find({}, function(err, allPosts){
if(!err){
allPosts.populate('_user', ['username']);
allPosts.exec(function (err, users){
if(err) console.log(err);
console.log(users);
});
res.render('main.njk', {
posts : allPosts,
title : 'Spaurk.net',
isLogged : req.session.isLogged,
user : req.session.user,
messages : req.flash('alert')
});
} else { return done(err); }
});
});
but that doesn't work of course.
So I was wondering if anyone with experience with this situation would be able to help me solve this.
Thanks a lot for any input.
EDIT, thanks to Daves help I was able to get the populate to work properly, I just cant pull the fields I want correctly with
Post.find({}).populate('_user').exec(function(err, allPosts){
In my loop {% for post in posts %}
, when I do post._user it shows the whole user schema, but when I do post._user.username it doesn't return anything. I am unsure as to why this is.
The proper way to structure a populate on a query is like this:
Post.find({})
.populate('_user')
.exec((err, allposts){...})
Then you will have an array of your Posts with the _user array populated. If you need to access a property of a user, you will need to do another loop through the _user array or specify with use you want to use _user[0].<property>

Save array of ObjectId in Schema

I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.

Mongoose Populate not working

Hello i have this Schema(called schema.js):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var RoomSchema = new Schema({
name: { type: String, required: true, index: { unique: true } },
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Room = mongoose.model('Room', RoomSchema);
var AvSchema = new Schema({
roomId: {type: Schema.Types.ObjectId, ref: 'Room'},
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Av = mongoose.model('Av', AvSchema);
module.exports = {
Room: Room,
Av: Av
};
in my Route file :
module.exports = function(app) {
var model = require('../models/Schema');
app.get('/api/rooms', function(req, res) {
model.Room.find(function(err, rooms) {
if (err)
res.send(err);
res.json(rooms);
});
});
app.get('/api/av', function(req, res) {
model.Av.find().populate('roomId').exec(function(err, av) {
if (err)
res.send(err);
res.json(av);
});
});
};
A pic of the db :
GET /api/rooms - response:
[{
"_id": "5444d0dd9a31437167eea816",
"name": "Single",
"people": 1,
"childrens": 1,
"total": 4
}, {
"_id": "5444d1009a31437167eea817",
"name": "Double",
"people": 2,
"childrens": 2,
"total": 10
}]
When i call api/rooms looks fine but when i call api/av i got an empty array [] .... Any idea what i do wrong? I should mention that i have inserted records in av collection for both roomsID
Thank you in advance.
By default, Mongoose pluralizes the model name to come up with the name of the collection, so Mongoose is looking in the avs collection instead of av.
You can explicitly set the collection name by passing that as the third parameter to model:
var Av = mongoose.model('Av', AvSchema, 'av');
I had the same issue but none of the answers worked for me.
I wanted to populate a document after it was queried.
This didn't work:
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ); // Doesn't work
} );
The Mongoose Documentation explains that when calling .populate() without a callback it won't be executed. Instead you need to use .populate().execPopulate():
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ).execPopulate(); // Works as expected
} );
Similar to CodyBugstein's answer, I'm posting why it wasn't working in my case, even though it's not the same case as OP's.
I was trying to populate the "pro" field of my schema in a .post('save') hook, as so:
mySchema.post('save', function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
doc.populate("pro"); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
However, the 2nd console.log was also logging the ObjectID instead of the doc.
After struggling with this for a solid hour and trying different approaches, I found out that all I had to do was use promises and call execPopulate() so that it returned a fully-fledged promise. I used async/await but you could use .then too:
mySchema.post('save', async function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
await doc.populate("pro").execPopulate(); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
This way, the 2nd console.log did log the entire pro doc as expected :)
Since this is the most popular result for the query
mongoose populate not working
I'll include the reason it wasn't working for me, even though it's not a direct answer to this already solved question, in the hopes it will help someone
The problem for me was that I had specified fields in select({..} but not the field I was trying to populate.
Don't forget to add the ref property to the schema for the property you are trying to populate. E.g.
// ...
const orderSchema = new mongoose.Schema({
userId: {
type: Types.ObjectId,
required: true
},
reservationId: {
type: Types.ObjectId,
required: true,
ref: 'Reservation' // <-- don't forget the ref
}
}, {
timestamps: true
})
// ...
See Mongoose Populate
Also check that your schema doesn't have depopulate in toJSON or toObject option set to true. (facepalm myself)
See all schema options
For me, it is was due to incorrect data. The Ids which I want to populate got deleted from the main table.
So when I do populate it didn't filled the populated data because the Ids were not in the table.
add this in your model
const productSchema = mongoose.Schema(
{
name: {
type: String,
required: [true, "product name must be provide"],
minlength: [3, "Name length minimum is 3"],
maxlength: [50, "Name length maximum is 50"],
trim: true,
},
price: {
type: Number,
required: [true, "price must be provide for product"],
default: 0,
},
description: {
type: String,
required: [true, "product description is required"],
trim: true,
},
{
timestamps: true,
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
}
);
Example:
productSchema.virtual("reviews", {
ref: "Review",
localField: "_id",
foreignField: "product",
justOne: false,
});
How to use:
products = await productModel.find({}).populate("reviews");
For me, the issue was that I did not require the model to be populated at the beginning of the file.

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

Resources