I'm messing about with a project - to track rewards for my kids - to learn Node and MongoDB/Mongoose and hitting an issue (it might be related to trying to get to grips with NoSQL with a background in SQL DBs).
I have two Schemas (that are applicable), Child and RewardDeposit.
Child
models.mongoose.Child = mongoose.model('Child', new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
nickname: String,
datecreated: {
type: Date,
default: () => Date.now(),
immutable: true,
},
datemodified: Date,
deposits: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'RewardDeposit'
}
]
}));
RewardDeposit
models.mongoose.RewardDeposit = mongoose.model('RewardDeposit', new mongoose.Schema({
for: String,
qty: Number,
datecreated: {
type: Date,
default: () => Date.now(),
immutable: true,
},
datemodified: Date,
child: {type: mongoose.Types.ObjectId, ref: "Child"}
}));
I run a test to create a Child, then create a RewardDeposit related to the child
var cid = "";
const c = new models.mongoose.Child;
c.firstname = "XXXXX";
c.lastname = "XXXXXX";
c.nickname = "XXXXXXX";
c.save()
.then((c) => {
cid = c._id;
const d = new models.mongoose.RewardDeposit;
d.for = "YYYY";
d.qty = 10;
d.child = c;
d.save()
})
No matter which way I write my populate/populated commands, the deposits array in the Child document is always empty
models.mongoose.Child.findById(cid).populate('deposits');
Do I have to have the RewardDeposits as a SubDocument? Can related top-level documents not be populated? Have I done my Schema wrong?
Child object
{
"_id": "6707fd5f98df9d7355b35580",
"deposits": [],
"datecreated": "2024-10-10T16:14:23.719Z",
"firstname": "XXXXX",
"lastname": "XXXXXX",
"nickname": "XXXXXXX",
"__v": 0
}
Reward Object
{
"_id": "6707fd6098df9d7355b35583",
"datecreated": "2024-10-10T16:14:24.630Z",
"for": "YYYY",
"qty": 10,
"child": "6707fd5f98df9d7355b35580",
"__v": 0
}
You created new RewardDeposit
and Child
but didn't update the Child
with RewardDiposit
's id. I think thats why the deposits
is empty on Child object
but you have reward object
cuz you .save()
it and forgot to update the Child
.
Took me a while to read your code cuz I haven't seen code written in nodejs like this.
But I think this code that I have written will work or give you some idea on how it might work.
var cid = "";
const c = new models.mongoose.Child;
c.firstname = "XXXXX";
c.lastname = "XXXXXX";
c.nickname = "XXXXXXX";
c.save() //you created the child with "deposits": []
.then((savedChild) => {
cid = savedChild._id;
const d = new models.mongoose.RewardDeposit;
d.for = "YYYY";
d.qty = 10;
d.child = savedChild; //your child filed is object so I would
//suggest you to write savedChild._id
return d.save(); //you created the reward deposit with child's id (line above this one)
})
//you forgot to update the child's deposits with the new deposit created above
.then((savedDeposit) => {
//now update the child's deposits array with the newly created RewardDeposit _id
return models.mongoose.Child.findByIdAndUpdate(
savedChild._id,
{ $push: { deposits: savedDeposit._id } },
{ new: true }
);
})
.then((updatedChild) => {
//check if the deposits in child is updated
console.log('Updated Child:', updatedChild);
})
.catch(err => {
console.error(err);
});
I would like to suggest you to use async/await
instead of Promise
in this case, cuz the code is hard to read and understand.
Hope this helps you.
Edit:
If you delete a document from RewardDeposit
, it will not automatically remove the reference from the deposits
of Child
document. Mongoose
doesnt provide cascading deletes like in some SQL
databases.
1. Remove manually.
const deleteRewardDeposit = async (depositId) => {
const deposit = await models.mongoose.RewardDeposit.findById(depositId);
if (deposit) {
//remove deposit reference from Child's deposits
await models.mongoose.Child.findByIdAndUpdate(deposit.child, {
$pull: { deposits: depositId }
});
//delete the reward deposit
await models.mongoose.RewardDeposit.findByIdAndDelete(depositId);
}
};
If there are a lot of deposits in a child, I would suggest you index the deposits id in Child model.If you dont know what am I talking about, search for Mongoose Indexes
or Mongoose Indexes.
Also, if you think there will be a lot of deposits
in a child. You'll have to make a new model for deposits
since, mongodb
can only hold upto 16 mb
of data per document
.
2. Use Mongoose middleware (Post-Hook).
You can set up a post-hook on the RewardDeposit
schema to automatically remove the reference from the Child
whenever RewardDeposit
is removed.
You'll have to write this following code at the bottom of you RewardDeposit
model file, and above the line mongoose.model("RewardDeposit", rewardDepositSchema)
.
models.mongoose.RewardDeposit.schema.post('findOneAndDelete', async function(doc) {
if (doc) {
await models.mongoose.Child.findByIdAndUpdate(doc.child, {
$pull: { deposits: doc._id }
});
}
});