How to use findById() for nested array's in mongoose? - node.js

Error Image Actually i've an Object in which i have an array of semesters, each semester has an array of subjects and each subject has an array of lessons.
I want to add lesson to its respective semester and subject.
I use findById to find respective semester from Object but when i use findById again to find particular subject from array of subj's.
It gives me error.
Semester.findById(req.body.semesterId).populate({ path: 'subjects' })
.exec((err, model) => {
[model.subjects.findById(req.body.subjectId, (err, model) => {
console.log(model)
})][1]
})
})

I would personally structure my Schema's like so:
const semesterSchema = mongoose.Schema({
number: { type: Number, required: true },
subjects: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Subject' }]
})
const subjectSchema = mongoose.Schema({
semester: { type: mongoose.SchemaTypes.ObjectId, ref: 'Semester', required: true },
title: { type: String },
lessons: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Lesson' }]
})
const lessonSchema = mongoose.Schema({
semester: { type: mongoose.SchemaTypes.ObjectId, ref: 'Semester', required: true },
subject: { type: mongoose.SchemaTypes.ObjectId, ref: 'Subject', required: true },
title: { type: String },
test: { type: Object }
})
What this does is provides cyclical references to my schemas which is quite nice in some cases.
To solve the case your describing, we could do something like this:
const { semesterId, subjectId } = req.body; // destructure variables needed from body
Semester
.findById(semesterId)
.populate({ path: 'subjects' })
.lean() // use lean() if you only require the document and not the entire mongoose object. i.e. you do not require .save(), .update(), etc methods.
.exec((err, semester) => {
const subject = semester.subjects.find(subject => subject._id === subjectId );
console.log(subject);
});
// ****** THIS ASSUMES YOU HAVE FOLLOWED MY DEFINED SCHEMA ABOVE ********
Alternatively, you could directly query the subject and populate the semester if you want that data like so:
const { semesterId, subjectId } = req.body;
Subject
.findById(subjectId)
.populate({ path: 'semester' })
.lean()
.exec((err, subject) => {
console.log(subject);
});
// ****** THIS ASSUMES YOU HAVE FOLLOWED MY DEFINED SCHEMA ABOVE ********

Related

MongoDB populate returning null

I am trying to populate my user schema with items but for some reason it does not populate anything in to the user schema. Could someone please take a look. I have 1 user and 1 item belonging to that user within my database but nothing is populating and I keep seeing null.
User Schema
var mongoose = require('mongoose')
var userSchema = mongoose.Schema({
name: {
type: String,
required: true
},
discordID: {
type: String,
required: true
},
discordImage: {
type: String,
required: true
},
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
}]
})
const User = module.exports = mongoose.model('User', userSchema)
Item Schema
var mongoose = require("mongoose")
var itemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
purchasedPrice: {
type: Number,
required: true
},
purchasedDate: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User"
}
})
const Item = module.exports = mongoose.model("Item", itemSchema)
Populate Code
app.get("/inventory", async (req, res) => {
try {
await req.user.populate({
path: 'items'
}).execPopulate()
console.log(req.user)
} catch (error) {
console.log(error)
}
res.status(200).render("inventory.ejs", { currentUser: req.user })
})
Objects in the DB:
Item:
User:
Used virtual method on user schema to create association
userSchema.virtual("items", {
ref: "Item",
localField: "_id",
foreignField: "author"
})
Worked fine with original code
I keep seeing null.
and
no its just empty
hints there are no items added to your user. You need to have some ids you can populate.
All populate does is convert an ObjectID into a document. There is no magic that will sync itemSchema.author with userSchema.items.
Hence, it's not enough to add the author to the item. You also need to add the item to the author.
So for example, you could add an item like this:
const item = new Item({author: user._id});
await item.save();
req.user.items.push( item );
await req.user.save();
Now when you log req.user, are there any items there?
Once you see objectIds, then you can go back and add that .populate('items') into the mix and I promise you it'll work.

How to populate data from nested object in mongoose

I am having schema like below:
const commentSchema = new Schema({
name: String,
surname: String,
email: String,
positions: [
{ detail: [{
type: Schema.Types.ObjectId,
ref: 'Position'
}],
progress: { type: String }, // keep track of the status
comment: { type: String } // keep track of internal notes
}],
});
Field detail contains array of mongo ids.
I am trying with below code but not getting the populated data:
const requirementsData = await Requirement.find({})
.populate({
path: "positions.detail",
model: Position,
})
.exec(function(err, docs) {
if(err){
console.log("err====", err)
}else{
res.render('candidates/show', {candidate: docs})
}
});
if I understand your problem you can do this
.populate({
path:'position',
populate:{path:'details'}
})

how to update a document that is a reference for creating a another document?

updating a document that is a reference to create another document.
I have to schemas: Teacher and Class.
when I'm creating a new Class I need a teacher in order for me to create a new Class and my Teacher schema has a classes property. Right now I can already create a new class.
My problem is I want to update the teacher that is used to create a new Class and store the created class to the classes property of the teacher.
here are my schemas:
//Class Schema//
const mongoose = require('mongoose');
const classSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
subject: { type: String, required: true },
teacher: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Teacher',
required: true
},
students: []
});
module.exports = mongoose.model('Class', classSchema);
//Teacher Schema//
const mongoose = require('mongoose');
const teacherSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: {
type: String,
required: true,
unique: true,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
},
password: { type: String, required: true },
classes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Class',
},
]
});
module.exports = mongoose.model('Teacher', teacherSchema);
//this my controller for creating a new Class//
const mongoose = require('mongoose');
const Class = require('../models/class');
const Teacher = require('../models/teacher');
exports.create_class = (req, res, next) => {
Teacher.findById(req.body.teacherId)
.then(teacher => {
if (!teacher) {
return res.status(404).json({
message: 'Teacher not found'
});
}
const _class = new Class({
_id: mongoose.Types.ObjectId(),
name: req.body.name,
subject: req.body.subject,
teacher: req.body.teacherId
});
return _class
.save()
})
.then(result => {
console.log(result);
res.status(201).json({
message: 'Class Created',
createdClass: {
_id: result._id,
name: result.name,
subject: result.subject,
teacher: result.teacher
},
req: {
type: 'GET',
url: 'http://localhost:3001/classes/' + result._id
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
}
I hope someone can help me. thank you in advance.
I know this is not an answer to your specific issue (there is an answer) but I think a bigger problem is your database design. You will run into problems of having to update two collections each time you create a class. If you want ot keep track of each class a teacher is assigned to, you just need to use the classes collection. You don't need to keep an array of classes for each teacher. You just need to insert a teacherId with each class created. Then you query that collection when you need to know what classes a teacher has:
// class schema:
const mongoose = require('mongoose');
const classSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
subject: { type: String, required: true },
teacher: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Teacher',
required: true
},
students: []
});
module.exports = mongoose.model('Class', classSchema);
and Teachers collection:
const mongoose = require('mongoose');
const teacherSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: {
type: String,
required: true,
unique: true,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
},
password: { type: String, required: true },
// teacher doesn't need classes. all the info you need is in classes
// if you want to know what class a teacher teaches, just query classes
});
module.exports = mongoose.model('Teacher', teacherSchema);
and finally (I've changed the fundamental of it)
exports.create_class = (req, res, next) => {
const {teacherId, subject } = req.body
// query class collection for both teacher AND class
// you have to write this function findClassByTeacher
// to query Class collection for subject AND teacher
Class.findClassByTeacher({ teacherId, subject })
.then(class => {
if (!class) {
// if no class is found by this teacher create a new class
const _class = new Class({
_id: mongoose.Types.ObjectId(),
name: req.body.name,
subject: req.body.subject,
teacher: req.body.teacherId
});
return _class.save()
}
return undefined
})
.then(result => {
console.log(result);
// check for result === undefined (class already exists)
// return res 'class already exists'
res.status(201).json({
message: 'Class Created',
createdClass: {
_id: result._id,
name: result.name,
subject: result.subject,
teacher: result.teacher
},
req: {
type: 'GET',
url: 'http://localhost:3001/classes/' + result._id
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
}
This is mostly untested so don't just use this code. the important thing is the principal behind modelling your schema. You want to use one-to-many relationships. for example, User has many posts, Post has many comments, Comments have many Likes etc. You don't have to design it this way with NoSQL, you can do what you have (or you're trying to achieve) but in my experience this way is so much more manageable, especially for long term scaling (if you ever need to do db migrations, complex queries on the db etc).
In your case, Teacher and Students have many Classes. You need to assess what data your app accesses frequently and model around that.
Check this out the various design patterns: https://docs.mongodb.com/manual/applications/data-models/
I'd give that a good read and based on what you do, choose the best model for your collection.

Mongoose find documents where field = req.body.user

I have a user schema and a post schema, wherein a user has many posts. I would like to return all posts that the user has on a route called '/post/dashboard'.
Here is my schemas:
let UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
default: null,
},
profile_pic: {
type: String,
default: '/img/profilepic.png',
},
posts: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
})
let PostSchema = new Schema({
title: {
type: String,
},
description: {
type: String,
}
original_poster: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
tags: {
type: [String]
}
})
So, for example something like:
app.get('/', (req,res) => {
Post.find({ original_poster: req.session.user }).then((posts) =>{
res.send(JSON.stringify(posts));
}) //where req.session.user is an id (the logged in user's object id or _id)
})
Essentially in sql syntax it might be something like:
SELECT * FROM POSTS WHERE ORIGINAL_POSTER = <req.session.user>
What is the proper way to return all posts by the req.session.user?
It seems that original_poster field represent a reference to User's model, If req.session.user is stored as a string you have to cast it to objectID:
const mongoose = require('mongoose');
...
let userId = mongoose.Types.ObjectId(req.session.user);
Post.find({ original_poster: userId }).then((posts) => {
res.send(JSON.stringify(posts));
});

Mongoose Populate - array

can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.

Resources