mongoose: Add an element into an empty child array under - mongodb

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'});

Related

Mongoose query document based on ref document id

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??

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;
})

How can I see the products per each category with mongoose

this is my schema for storing products using mongoose as below.
const mongoose = require("mongoose");
const mongoosePaginate = require("mongoose-paginate-v2");
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
required: true,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
productImage: {
type: String,
required: true,
},
description: {
type: String,
},
createdAt: {
type: Date,
default: new Date(),
},
deletedAt: {
type: Date,
},
});
productSchema.plugin(mongoosePaginate);
const productModel = mongoose.model("Product", productSchema, "Product");
module.exports = productModel;
and this how I have the schema for storing categories that products are related to
const mongoose = require("mongoose");
const categorySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
product: { type: mongoose.Schema.Types.ObjectId, ref: "Product" },
});
const categoryModel = mongoose.model("Category", categorySchema, "Category");
module.exports = categoryModel;
What I don´t know is how to populate my controller.
getAll: async (req, res) => {
const limitPage = parseInt(req.query.limit, 10) || 10;
const pageChange = parseInt(req.query.page, 10) || 1;
Product.paginate({}, { limit: limitPage, page: pageChange })
.then((result) => {
return res.status(200).json({
message: "GET request to all getAllProducts",
dataCount: result.length,
result: result,
});
})
.catch((err) => {
console.log(err);
res.status(500).json({
error: err,
});
});
},
Please help, I don´t understand why it not being populated and how to see the categories displayed with the categorie they belong to.
You should probably include populate in your query like so:
...
Product.paginate({}, { limit: limitPage, page: pageChange }).populate('category')
...
Note: Are you sure you want to have a 1-1 relation between products and categories. Because this is what you achieve if you set the relation like you did on both schemas. If yes, you should find a way to ensure that this 1-1 relation is enforced each time you save or update objects.

mongoose .populate() is not populating even with variable set to ObjectId

Here is my model for "product" schema:
const productSchema = new Schema({
productName: String,
productCategory: {
type: Schema.Types.ObjectId,
ref: "category",
},
productPrice: Number,
productImageUrl: String,
});
Here is the router for the GET method:
router.get("/", async (req, res) => {
try {
const products = await ProductModel.find().populate("category");
res.status(200).json({ erorr: false, products });
} catch (err) {
res.status(500).json({ error: true, err });
}
});
What I actually get is this:
"error": false,
"products": [
{
"_id": "6009f4bfd397734920c93ce8",
"productName": "Milk",
"productCategory": "6009d244332f2f22c40f90b4",
"productPrice": 8,
"productImageUrl":""
In Mongo Compass I can see that "productCategory" is set with ObjectId value.
Any ideas what am I doing wrong?
Thanks!
Try this one:
const mongoose = require('mongoose');
const Types = mongoose.Schema.Types;
const productSchema = new Schema({
productName: String,
productCategory: {
type: Types.ObjectId,
ref: "category",
},
productPrice: Number,
productImageUrl: String,
});
const products = await ProductModel.find({}).populate({path:"productCategory"});
you should put the key that has ref to populate like this:
const products = await ProductModel.find().populate("productCategory");

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