Populate an array in Mongoose - mongodb

I am building a search query for training sessions that will return me return details of a session, populating data from the coach (ObjectId) and the participants (Array of ObjectIds). I can populate the coach but I can not populate the participants. My session schema is:
const mongoose = require('mongoose');
import { timestamp } from "./plugins/timestamp"
import { User } from './index'
const SessionSchema = new mongoose.Schema({
coach: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
title: { type: String, required: true, default: "Lacrosse Training Session" },
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }]
});
SessionSchema.plugin(timestamp);
export const Session = mongoose.model('Session', SessionSchema);
And I am trying to populate with:
const session = await Session.findById(req.params.id).populate('coach').populate('participants');
Output
When I use only populate('coach'), I get something like:
coach: {address: {city: "Joes"}, name: "John John", …} <= <= <= POPULATED
participants: ["5ea43590f105a4188358210f", "5ea43590f105a4188358210e", "5ea43590f105a41883582115"]
But when I use populate('coach').populate('participants'), I get the same coach, but empty participants (participants: [])
Why is that? How can I populate each element of the participants array?
Thank you

you can use one of the following:
1-
const session = await Session.findById(req.params.id).populate([{ path: 'coach' }, { path: 'participants' }])
2-
const session = await Session.findById(req.params.id).populate({ path: 'coach' }).populate({ path: 'participants' });
also make sure that these participants Ids are already exist in the User collection

Related

How to Find record by ObjectID's property in Mongodb?

Suppose I have 2 models like Orders and Customers.
const orders = mongoose.Schema({
customer_id: {
type: mongoose.Schema.ObjectId
required: true,
ref: 'Customer',
},
OrderNo: {
type: String,
required: true,
})
const customers = mongoose.Schema({
name: {
type: String,
required: true,
})
I want to filter orders based on customer's name as per below.
let query = {
'customer_id.name': { $regex: '.*AMAZON.*' },
}
await Orders.find(query)
but its not working properly. I'm new to mongodb. Can anyone share feasible solution ? Thanks
Try to populate and filter the resulting fields:
await Orders.find({})
.populate({ path: 'customer_id', match: { name: { $regex: '.*AMAZON.*' } } })
.exec();

MongoDB creating cross-referenced documents in Mongoose?

I am using Mongoose for a content-app that stores exhibition reviews. I decided to use references rather than subdocuments, so each Exhibition stores an array of _Ids for it's Reviews and each Review stores the _Id of it's Exhibition.
I want to require both fields, but the problem I run into is, what order to create them in?
I don't like my work around because I use an empty array for thereviews when creating an Exhibition, then exhibition.reviews.push() if the subsequent Review creation is successful, but have to Exhibition.deleteOne() if it throws an error.
Is this robust or am I risking having Reviews with no Exhibition reference or Exhibitions with no Reviews ?
Review model:
const reviewSchema = mongoose.Schema({
content: {
type: String,
required: true
},
exhibition: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Exhibition',
required: true
}
})
const Review = mongoose.model('Review', reviewSchema)
Exhibition model:
const exhibitionSchema = mongoose.Schema({
title: {
type: String,
required: true
},
reviews: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Review',
required: true
}]
})
const Exhibition = mongoose.model('Exhibition', exhibitionSchema)
My work-around:
try {
const exhibition = await Exhibition.create({
title: 'title'
reviews: []
})
try {
const review = await Review.create({
review: 'review'
exhibition: exhibition._id,
})
exhibition.reviews.push(review._id)
await exhibition.save()
} catch {
Exhibition.deleteOne({ _id: exhibition._id })
}
} catch (err) {
return handleError(err)
}
})
Is there a better way to do this?

Mongoose populate subdocument returns null

I want to populate a query with mongoose, using the "populate" method. I'm using the most recent/stable version of mongoose. The relevant parts of my schema are defined as shown below. The problem is that when I try to populate const sessions = await Session.find().populate({ path: "coach", select: "name email" }), I always get "coach": null.
I expected it to simply return the sessions that match my query together with the coach (user) object. What am I doing wrong here?
UserSchema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "email is required"],
unique: [true, "email must be unique"],
match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, "Invalid email"]
},
name: {
type: String,
required: [true, "name is required"]
},
(....)
});
export const User = mongoose.model('User', UserSchema);
SessionSchema
const mongoose = require('mongoose');
const SessionSchema = new mongoose.Schema({
coach: { type: mongoose.Schema.ObjectId, ref: 'User', required: true },
location: {
street: { type: String }
},
(......)
});
export const Session = mongoose.model('Session', SessionSchema);
I think the coach type should be the following (Schema.Types seems to be missing):
coach: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
I've found it. Sorry, guys. The code is ok. The problem was that I was seeding my DB with entries that had a "id" property, not a "_id" property. So, the "id" was not getting saved properly and thus the response was actually null.

Load nested virtual during mongodb query

I'm new to using a key other than ObjectId to link data from other collections. Currently, I have appointments with various other data I'd like to bring in so I can evaluate whether payment is due or not.
My query worked, except it doesn't bring in the plan information for each patient. I understand that it makes a separate query for each populate, so I'd have to do it after I populate the patient information with populate('patientID'):
const appts = await Appt.find(searchParams)
.populate('patientID')
.populate('patientID.plan')
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
The above doesn't work for bringing in the nested JSON of the plan information, but it DOES work for bringing in the patient collection, status, and type. Only patientID.plan populate doesn't work.
My schemas:
const familySchema = new mongoose.Schema({
ID: {
type: Number,
index: true
},
family: String
});
const paymentplanSchema = new mongoose.Schema({
ID: {
type: Number,
index: true
},
plan: String,
planamt: Number
});
const patientSchema = new mongoose.Schema({
ID: {
type: Number
},
familyID: Number,
first: String,
last: String,
careplanID: Number,
otherData: variousTypes
});
patientSchema.virtual('plan', {
ref: 'PaymentPlan', // The model to use
localField: 'careplanID', // Find people where `localField`
foreignField: 'ID' // is equal to `foreignField`
});
patientSchema.pre('find', function() {
this.populate('plan');
});
const typeSchema = new mongoose.Schema({
ID: Number,
appttype: String,
abbr: String,
amt: Number,
code: String,
length: Number
});
const statusSchema = new mongoose.Schema({
ID: Number,
status: String
});
const apptSchema = new mongoose.Schema({
ID: Number,
patientID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Patient'
},
oldPatientID: Number,
status: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptStatus'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApptType'
},
scheduled: Date,
note: String
});
mongoose.model('Appt', apptSchema);
mongoose.model('ApptStatus', statusSchema);
mongoose.model('ApptType', typeSchema);
mongoose.model('Patient', patientSchema);
mongoose.model('PaymentPlan', paymentplanSchema);
How do I get the patient data to load WITH the plan data? I don't get what I'm doing wrong, and I've got other things I'd like to connect in this way (via index instead of ObjectId) but just don't get what I'm doing wrong.
UPDATED TO ADD MORE DETAIL:
My query on the backend to get the appointments is this:
module.exports.search = async (req, res) => {
console.log('GET the appts');
const searchParams =
req.params.query === 'today'
? { scheduled: { $gt: new Date(dayStart), $lt: new Date(dayEnd) } }
: req.body;
console.log(searchParams);
try {
const appts = await Appt.find(searchParams)
.populate({
path: 'patientID',
populate: { path: 'plan' }
})
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
if (!appts) {
console.log(`No appointments found`);
}
appts.forEach(p => {
const patient = p.patientID ? p.patientID.nickname : 'NONE';
const plan =
p.patientID && p.patientID.plan ? p.patientID.plan.planamt : 0;
console.log(patient, plan);
});
console.log(appts.length, 'appts found');
res.send(appts);
} catch (err) {
console.log(`Error`, err);
return res.status(500).send(err);
}
};
In the console, It's logging correctly (example):
CarF 60
8075 'appts found'
In the frontend, all the objects are populated EXCEPT patientID.plan. The patientID object does not include a plan field on any of the entries. patientID, status, and type all populated the corresponding objects.
WHY is this logging on the backend, but not visible on the frontend?
You should be able to do it by passing a path option to populate():
const appts = await Appt.find(searchParams)
.populate('patientID')
.populate({
path: 'patientID',
populate: {path: 'plan'}
})
.populate('status')
.populate('type')
.sort({ scheduled: -1 });
See https://mongoosejs.com/docs/populate.html#deep-populate in official docs

Ensure a unique index on nested reference on a mongoose schema

What I want is that a user can like a post only once, hence I uniquely indexed the user in the likes array to ensure the same, but it isn't working and I can't find out what is wrong here .
The schema looks like this :
const mongoose = require('mongoose')
const postSchema = new mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User' // User model
},
text: {
type: String,
required: [true, 'Post must have some text']
},
likes: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}
],
comments: [
{
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: [true, 'Comment must have some text']
},
addedAt: {
type: Date,
default: Date.now
}
}
],
createdAt: {
type: Date,
default: Date.now
}
})
postSchema.pre(/^find/, function(next) {
this.populate({
path: 'author',
select: 'name avatar'
}).populate({
path: 'comments.author',
select: 'name avatar'
})
next()
})
// Ensure a user can like a post only once
postSchema.index({ 'likes.user': 1 }, { unique: true })
const Post = mongoose.model('Post', postSchema)
module.exports = Post
However when I send a post request to like a post twice via the same user it
shows no error.
Here is the postman output
I have tried both the ways listed in this, but none of them worked in this case.
Mongoose Index on a field in nested document
How do I ensure a user can like a post only once from the schema itself ?
Try saving likes in this format in the database
likes:[{type:mongoose.Schema.Types.ObjectId,ref: 'User'}]
making it
likes:[ObjectId("5af03111967c60501d97781f")]
and when the post like API is hit do
{$addToSet: {likedBy: userId}}
in update query,addToSet ensures no duplicate ids are maintained in the array.