Mongoose query document based on ref document id - mongodb

I have the two schemas:
const Schedule = mongoose.model(
"Schedule",
new mongoose.Schema({
date: Date,
timeSlots: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "TimeSlot",
},
],
})
);
and
const TimeSlot = mongoose.model(
"TimeSlot",
new mongoose.Schema({
slot: String,
user_id: String,
isBooked: Boolean,
isCompleted: Boolean,
service: String,
description: String,
price: String,
})
);
now first i have made this query:
const user_reservations = await TimeSlot.find({
user_id: user._id,
});
Then i get back a list of documents. Now i want to find the date associated with those time slots. And how do i do that? For every time slot i want to return the date it belongs to. I have tried inn a loop:
const reservationArr = user_reservations.map(async (timesSlot) => {
const userTimeSlot = await Schedule.findOne().populate({
path: "timeSlots",
match: { _id: timesSlot._id.valueOf() },
});
return userTimeSlot;
});
But no luck... Any help??

Related

Mongoose nested populate with select and limit

How can I limit the number of pictures?
CommentBy is a ref in Comment, which is a ref in Post.
const posts = await Post.find(filter).populate({
path: 'comments',
populate: {
path: 'commentBy',
select: 'first_name last_name pictures',
},
});
Post:
const postsSchema = new Schema(
{
text: String,
comments: { type: [ObjectId], ref: 'Comment' }, // comment ref here
reactions: {
type: [ObjectId],
ref: 'Reaction',
},
},
);
Comment:
const commentsSchema = new Schema({
commentBy: {
type: ObjectId,
ref: 'User',
},
text: {
type: String,
},
});
User:
const userSchema = new Schema({
first_name: {
type: String,
},
last_name: {
type: String,
},
pictures: {
type: [String],
},
});
I don't think that in mongoose you can limit a specific field of a populated document.
You could try to use lean to return an array of objects and manipulate that in plain JS:
let posts = await Post.find(filter).populate({
path: 'comments',
populate: {
path: 'commentBy',
select: 'first_name last_name pictures',
},
}).lean().exec();
posts = posts.map((post) => {
const pics = post.comments.commentBy.pictures
if (pics.length <= 10) return post;
post.comments.commentBy.pictures = pics.slice(0, 10);
return post;
})

mongoose: Add an element into an empty child array under

I have the following schema, and I have a document of the story in mongodb, this story doesn't have values for key "fans", which is an array.
I would like to add an element to this array. However, I tried to use fans.push, fans.pop or fans = [element], it doesn't work. Please help me to understand what is the best way of doing this.
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
const story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
const fan = new Person({
_id: new mongoose.Types.ObjectId(),
name:'Fan 001',
age:38
});
fan.save(function(err){
if (err) return handleError(err);
const story1=Story.findOne({title:'Casino Royale'});
story1.fans=[fan._id];
story1.save(function (err){
if (err) return handleError(err);
});
});
when I run this script, I have got the following error:
So to make this whole thing work you need a few steps:
Step 1: You need to save the story1 you created else the findOne wont return anything
Step 2: You need to await database calls since they are async
I will provide a code making use of the database update methods, it is a lot cleaner and faster to directly push it in the database.
So here is your code corrected:
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
const author = { name: 'asdsd', _id: "aaaaaaaaaaaaaaaaaaaaaaaa" } // Note I hardcoded an id here
const story1 = new Story({
title: 'Casino Royale',
author: author._id // Assign the _id from the person
});
await story1.save() // Await
const fan = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Fan 001',
age: 38
});
await fan.save(async function(err) { // Await since we have to await the database
if (err) return handleError(err);
const story1 = await Story.findOne({ title: 'Casino Royale' }); // Await database
console.log(story1)
story1.fans.push(fan._id);
await story1.save(function(err) { // Await again
if (err) return handleError(err);
});
});
Here is the code in a better version (Note that the schema doesnt have an _id anymore since it is provided by mongoDb):
const personSchema = Schema({
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
const author = { name: 'asdsd', _id: "aaaaaaaaaaaaaaaaaaaaaaaa" } // Note I hardcoded an id here
// Create a Story that is immediately saved in the database and gets an _id by mongoDb
await Story.create({
title: 'Casino Royale',
author: author._id
})
// Create a Person that is immediately saved in the database and gets an _id by mongoDb
const fan = await Person.create({
name: 'Fan 001',
age: 38
})
// Add the Person to the stories fans array
const story1 = await Story.findOneAndUpdate(
{ title: 'Casino Royale' },
{ $push: { 'fans': fan._id } },
{ new: true })
console.log(story1)
here const story1=Story.findOne({title:'Casino Royale'}); findOne is not returning the document hence save is not defined for that.
as mentioned in the documentation it returns a query
try writing your code using promise and await for the response
const story1 = await Story.findOne({title:'Casino Royale'});

How to implement inner join in mongoose

Problem:
I want to achieve an inner join in mongoose for a model which has dynamic ref and direct ref to another model(s). Pls refer to the sample schema and models below.
const schema1 = mongoose.schema({
on: {
type: Schema.Types.ObjectId,
required: true,
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['Model2', 'Model3']
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company'
}
});
const Model1 = mongoose.model('Model1', schema1);
const schema2 = mongoose.schema({
name: {
type: String,
maxlength: 100
},
email: {
type: String,
maxlength: 100
}
});
const Model2 = mongoose.model('Model2', schema2);
const schema3 = mongoose.schema({
name: {
type: String,
maxlength: 100
},
email: {
type: String,
maxlength: 100
}
});
const Model3 = mongoose.model('Model3', schema3);
const companySchema = mongoose.schema({
companyName: {
type: String,
maxlength: 100
}
});
const company = mongoose.model('Company', companySchema);
const res = await models.Model1
.find()
.populate({
path: 'on',
match: {
'name': keyword
}
})
.populate({
path: 'company',
match: {
'companyName': keyword
}
});
The above find returns documents even if on and company returns null value (as mongoose populate implements left join by default).
Expected Result: I want to fetch documents from model1 only if it matches the keyword with name field in model2 or model3 or with company name field in company model.
How to achieve this? Your help is much appreciated.
1.Let's update the schema for "Model 1" like below:-
const schema1=mongoose.schema( {
ref: {
kind: String, // <-- This will store the Model Name (Model2 or Model3) when you execute the insert query.
item: {
type: mongoose.Schema.Types.ObjectId, // <-- This will store the Reference ID of another table (Model2 OR Model3)
refPath: 'ref.kind', fields: String,
}
,
}
, company: {
type: Schema.Types.ObjectId, ref: 'Company'
}
});
Keep the schema for Model2 , Model3 & Company as it is. No need to make any change here.
When you want to find :-
await models.Model1.find().populate({ path: 'ref.item' });
That's it. This should work.

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

Mongoose, proper way to update documents with deep nested schemas

I would like to know what would be the most efficient way to update a document that also has a nested schema in it. Normally I would just use Model.findByIdAndUpdate(id, updatedValues) ..., however if I try to do that with a document that has a deep nested schema I get this error: CastError: Cast to Embedded failed for value "{ _id: 5b1936aab50e727c83687797, en_US: 'Meat', lt_LT: 'Mesa' }" at path "title".
My Schemas look something like:
const titleSchema = new Schema({
en_US: {
type: String,
required: true
},
lt_LT: {
type: String,
default: null
}
})
const categorySchema = new Schema({
title: titleSchema
});
const ingredientSchema = new Schema({
title: {
type: titleSchema,
required: true
},
category: categorySchema,
calories: {
type: Number,
min: 0,
default: 0
},
vitamins: [String]
})
And I try to update like so:
{
title: {
en_US: 'Pork',
lt_LT: 'Kiauliena'
},
category: {
_id: '5b193a230af63a7e80b6acd8',
title: {
_id: '5b193a230af63a7e80b6acd7'
en_US: 'Meat',
lt_LT: 'Mesa'
}
}
}
Note, I get the new category object from a separate collection using just the category _id, but the final update object that goes into the findByIdAndUpdate() function looks like the one above.
The only workout I found is to remove the category from the updated values, update the document via findByIdAndUpdate(), get the updated document, assign the new category to it and save it.
It works just fine and all, but that requires two calls to the database, is it possible to do it in just one?
Try updating your schema like this:
const titleSchema = new Schema({
en_US: {
type: String,
required: true
},
lt_LT: {
type: String,
default: null
}
});
const ingredientSchema = new Schema({
title: {
type: titleSchema,
required: true
},
category: {
title: titleSchema
},
calories: {
type: Number,
min: 0,
default: 0
},
vitamins: [String]
});