Mongoose populate not working upto 3 levels - node.js

I am trying to populate a fields that is nested inside my model but it is not populating.
This is a field inside my model.
pendingChanges: {
credentials: {
university: {
name: { type: String },
major: { type: String },
majorGpa: { type: Number },
},
school: {
name: { type: String },
degreeType: { type: String },
degree: { type: String },
},
subjects: [{ type: mongoose.Schema.Types.ObjectId, ref: 'subject' }],
workExperience: {
type: { type: String },
from: { type: Date },
to: { type: Date },
},
},
},
I am trying to populate subjects key nested inside.
This is what I have done so far.
const teacher = (await this.findById(id))
.populate({
path: 'pendingChanges',
populate: {
path: 'credentials',
populate: {
path: 'subjects',
},
},
});

I found the solution. Here's what I did to make it work.
const data = await this.findOne(query)
.populate({
path: 'pendingChanges.credentials.subjects',
});

Related

using "or" operator when using .populate on a query?

I have my invoices model which has a "sales" field ,which is array of sales. In my sales model I have item field which either can come from Vehicles schema or AfterSale schema.
export const Sales = new Schema<ISale>(
{
customer: { type: Schema.Types.ObjectId, ref: "Customer", required: true },
category: { type: String, enum: enu, required: true },
item: {
type: Schema.Types.ObjectId,
required: true,
ref: (doc) => doc.category,
},
total: { type: Number, required: true },
discount: { type: Number },
purchaseDate: { type: Date, required: true },
branch: { type: Schema.Types.ObjectId, ref: "Branch", required: true },
},
{ timestamps: true },
);
I want to populate the sales.item on my invoice schema, but the problem is , since item can reference to multiple schemas, I can only populate one of them.
async findAll(req: any, query: SharedPreferences): Promise<InvoiceClass[]> {
const invoices = this.invoiceModel
.find(req.searchObj)
.sort({ [query.sort]: query.orderBy === "desc" ? -1 : 1 })
.populate([
"customer",
"sales",
{
path: "sales",
populate: [
{
path: "customer",
model: "Customer",
},
{
path: "branch",
model: "Branch",
},
{
path: "item",
model: "Vehicle",
},
{
path: "item",
model: "AfterSale", //in this case only AfterSale will be populated since
// its last and has same path
}
],
},
]);
my perfect solution would have been using the or operator , but that doesnt work
async findAll(req: any, query: SharedPreferences): Promise<InvoiceClass[]> {
const invoices = this.invoiceModel
.find(req.searchObj)
.sort({ [query.sort]: query.orderBy === "desc" ? -1 : 1 })
.populate([
"customer",
"sales",
{
path: "sales",
populate: [
{
path: "customer",
model: "Customer",
},
{
path: "branch",
model: "Branch",
},
{
path: "item",
model: { $or: ["Vehicle", "AfterSale"] },
},
],
},
]);
any reference/help will be appreciated, thanks.

Insert field in object

I need to insert a field (req.body.firstname) in firstname object.
If such a field(req.body.firstname) already exists in the object, add id(req.body.user) to this array.
if such a field(req.body.firstname) does not exist, create it and add id(req.body.user) to it.
I think we should use findOneAndUpdate, but I can’t understand how to use it.
router.post('/bio/firstname', (req, res) => {
console.log(req.body.id) //d9aa8566-75fe-4108-a72e-1b67e79cf40c
console.log(req.body.user) //5e7f164771c90130441c0102
console.log(req.body.firstname) //кот
Habalka.find({
_id: req.body.id
})
.then(habalka => {
console.log(habalka[0].bio.firstname)
Habalka.findOneAndUpdate(
{
"bio.firstname": req.body.firstname
},
{$push: {[`bio.$.firstname.${req.body.firstname}`]: '12123'}},
{new: true}
)
.then((data) => {
res.json({error: false, data: data})
});
})
});
model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const HabalkaSchema = new Schema({
_id: {
type: String
},
bio: {
firstname: {},
lastname: {},
middlename: {},
company: {}
},
rating: [
],
files: [
{
_id: {
type: String
},
destination: {
type: String
},
filename: {
type: String
},
path: {
type: String
},
folder: {
type: String
},
info: {
size: {
type: Number
},
mimetype: {
type: String
},
encoding: {
type: String
},
originalname: {
type: String
},
fieldname: {
type: String
},
},
date: {
type: Date,
default: Date.now
},
bio: {
type: Object
},
userId: String,
guessId: {},
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Habalka = mongoose.model('habalka', HabalkaSchema);

removing a string within nested array mongoose

const AllPostsSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
posts: [{
postid: {
type: String
},
title: {
type: String
},
category: {
type: String
},
subcategory: {
type: String
}, category: {
type: String
},
description: {
type: String
},
name: {
type: String
},
price: {
type: Number
},
email: {
type: String
},
phonenumber: {
type: Number
},
language: {
type: String
},
make: {
type: String
},
model: {
type: Number
},
odometer: {
type: Number
},
condition: {
type: String
},
state: {
type: String
},
town: {
type: String
},
city: {
type: String
},
links: [{ type: String }],
date: {
type: Date,
default: Date.now
}
}]
})
AllPosts.findOneAndUpdate({ 'user': req.query.userid },
{ $pull: { 'posts': { 'links': req.query.link } } }
)
.then(post => console.log(post))
i need to find a specific user and within that user match the post id then remove one of the links in links array. when I do it like above it removes the whole array instead i want it to remove specific link within links array in posts arrayy.
Each user has one or more than one posts. I need to update a single post of a specific user. if a user wants to delete an image i delete that from amazon s3 then, i need to remove the link from that post link array so it doesnt create broken img tags in the front end.
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'links': req.query.link } }
)
.then(post => console.log(post))
this also didnt work.
Solved. For future reference :
AllPosts.findOneAndUpdate({ 'user': req.query.userid, 'posts.postid': req.query.postid },
{ $pull: { 'posts.$.links': req.query.link } }
)
.then(post => console.log(post))

Mongoose sub field aggregation with full text search and project

I have a Mongoose model called Session with a field named course (Course model) and I want to perform full text search on sessions with full text search, also I wanna aggregate results using fields from course sub field and to select some fields like course, date, etc.
I tried the following:
Session.aggregate(
[
{
$match: { $text: { $search: 'web' } }
},
{ $unwind: '$course' },
{
$project: {
course: '$course',
date: '$date',
address: '$address',
available: '$available'
}
},
{
$group: {
_id: { title: '$course.title', category: '$course.courseCategory', language: '$course.language' }
}
}
],
function(err, result) {
if (err) {
console.error(err);
} else {
Session.deepPopulate(result, 'course course.trainer
course.courseCategory', function(err, sessions) {
res.json(sessions);
});
}
}
);
My models:
Session
schema = new mongoose.Schema(
{
date: {
type: Date,
required: true
},
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course',
required: true
},
palnning: {
type: [Schedule]
},
attachments: {
type: [Attachment]
},
topics: {
type: [Topic]
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
address: {
type: Address
},
quizzes: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'quiz'
},
path: {
type: String
},
limitPlaces: {
type: Number
},
status: {
type: String
},
available: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: new Date()
},
updatedAt: {
type: Date
}
},
{
versionKey: false
}
);
Course
let schema = new mongoose.Schema(
{
title: {
type: String,
required: true
},
description: {
type: String
},
shortDescription: {
type: String
},
duration: {
type: Duration
},
slug: {
type: String
},
slugs: {
type: [String]
},
program: {
content: {
type: String
},
file: {
type: String
}
},
audience: [String],
requirements: [String],
language: {
type: String,
enum: languages
},
price: {
type: Number
},
sections: [Section],
attachments: {
type: [Attachment]
},
tags: [String],
courseCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course-category',
required: true
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
status: {
type: String,
default: 'draft',
enum: courseStatus
},
path: {
type: String
},
cover: {
type: String,
required: true
},
duration: {
type: Number,
min: 1
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date
}
},
{ versionKey: false }
);
I am not sure if what I tried is gonna bring me what I want and I am getting this error concerning the $unwind operator:
MongoError: exception: Value at end of $unwind field path '$course'
must be an Array, but is a OID
Any kind of help will be really appreciated.
You can try below aggregation.
You are missing $lookup required to pull course document by joining on course object id from session document to id in the course document.
$project stage to keep the desired fields in the output.
Session.aggregate([
{
"$match": {
"$text": {
"$search": "web"
}
}
},
{
"$lookup": {
"from": "courses",
"localField": "course",
"foreignField": "_id",
"as": "course"
}
},
{
"$project": {
"course": 1,
"date": 1,
"address": 1,
"available": 1
}
}
])
Course is an array with one course document. You can use the $arrayElemAt to project the document.
"course": {"$arrayElemAt":["$course", 0]}

Sort array field with Mongoose

I'm trying to order the next model by ts (timestamp):
var Schema = new mongoose.Schema({
info: {
name: { type: String, trim: true, validate: [
{ validator: validations.user.maxName, msg: 'The name must be shorter' }
]}
},
gender: { type: String, trim: true, enum: ['Male', 'Female'] },
notifications: [{
story: {
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Story' },
video_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Video' }
},
video: {
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Video' },
video_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Video' }
},
type: { type: String },
read: { type: Number, default: 0 }, // 0 - Unread, 1 - read
ts: { type: Date, default: Date.now }
}]
}, { timestamps: { createdAt: 'created_at' } });
This is the code I'm trying to use in order to order these notification elements is what it follows:
UserSchema.statics.getNotifications = function (user_id) {
return this.findById(user_id)
.select('notifications')
.populate({
path: 'notifications.story.video_id',
select: '_id user story',
populate: ([{
path: 'user',
select: '_id nickname info.thumbnail'
}, {
path: 'story',
select: 'title'
}])
})
.populate({
path: 'notifications.video.video_id',
select: '_id user story parent',
populate: ([{
path: 'user',
select: '_id nickname'
}, {
path: 'story',
select: '_id'
}])
})
.sort({ 'notifications.ts': -1 })
.exec();
};
But instead of sorting my notifications, I guess I'm sorting the users that return my query with all the notifications.
Is there any way to sort for a given user, the notifications?
Inside your populate you want to sort you can
.populate({ path: 'theoneyouwanttosort', options: { sort: { createdAt: -1 } } })
Hope that can help you :)
Thanks to #JohnnyHK here bellow I leave the answer of my question:
Schema.findByIdAndUpdate(user_id, {
$push: {
notifications: {
"$each": [{
story: {
parent: mongoose.Types.ObjectId(video_bookmarked),
video_id: mongoose.Types.ObjectId(video_id),
},
type: 'respond-video'
}],
"$sort": { ts: -1 }
}
},
$inc: { count_notifications: 1 }
}).exec();

Resources