Mongoose: Pushing values to an array inside a nested object - node.js

I have been searching for an answer for this for hours - there seem to be some similar questions without a working answer - so if you can help me solve it then you can have my car, my house and my first born - whatever you want!
When I create a record for the model below, I need a way to insert values to the participants nested array:
const recordSchema = new Schema (
{record:
[
{
eventDate: Date,
game: {
type: Schema.Types.ObjectId
,ref: "Game"
},
winner: {
type: Schema.Types.ObjectId
,ref: "Member"
},
particpants: [
{
type: Schema.Types.ObjectId
,ref: "Member"
}
]
},
{
timestamps: true,
}
],
}
)
Creating a record works fine for all fields except participants, which remains unpopulated:
Record.create({record: { game: game, winner: winner, participants: participants }})
I have tried several methods to try to get to that nested array and (for example) update after creation, but so far I haven't been able to get there. Keep in mind this needs to create programatically - I can't hard code in any values.
Many, many, many thanks if you are able to help me with this.
Edit: adding the data being input as comment said this would be useful:
console logging the variables right before the create returns the following: game: '6220ca4d8179d6685b4c05b7', winner: '6225ed9f21a3b31c86098b8d', participants: [ '6225ec458e34373cf7337d68', '6225ed9f21a3b31c86098b8d' ]

You have a typo in your schema, as you can see your schema expects particpants but in your .create() you are passing participants.
You are missing an i after c in schema.
participants: [{ type: Schema.Types.ObjectId, ref: "Member" }]

Related

Mongodb Relationship: Posts and Comments (ref v sub-documents)

I know there are a lot of similar questions, but they're too old and since Mongodb has evolved alot for last 5-6 years I am looking for a good schema design.
Goal: I want to have a post with comments by users.
What I have designed so far is:
Separate post model:
const projectSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
title: { type: String, required: true },
image: { type: String, default: undefined },
description: { type: String, required: true, minLength: 200, maxlength: 500 },
comments: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Comment'
}],
state: { type: Boolean, default: true },
collaborators: { type: Array, default: [] },
likes: { type: Array, default: [] }
})
And a separate comments model:
const commentSchema = new mongoose.Schema({
comment: { type: String, required: true },
project: { type: String, required: true, ref: 'Project' },
user: { type: String, required: true, ref: 'User' }
})
The reason I am going for the relational approach is because if the comments increase to say 10,000 in number, it will increase the size of schema by alot.
This way, no matter how many comments we can populate them using their IDs, also, we will have different collection for comments iself.
Reference : one-to-many
Is this a good approach for my project?
The way I am querying the comments from one single post:
const project = await Project.findById(
new mongoose.Types.ObjectId(req.params.projectId)
).populate({
path: 'comments',
populate: { path: 'user' }
}).lean()
Whether it's a good design depends how many comments per post do you expect, and what query will be performed on your app.
There's a good blog from mongodb.com on how to design your database schema
The common design is:
One to Few (Use embed)
One to Many (Use embed reference)
One to squillions (The usual relational database one-to-many approach)
Summary is:
So, even at this basic level, there is more to think about when designing a MongoDB schema than when designing a comparable relational schema. You need to consider two factors:
Will the entities on the ā€œNā€ side of the One-to-N ever need to stand alone?
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
Based on these factors, you can pick one of the three basic One-to-N schema designs:
Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
There is also a blog about advanced schema design which is worth the read.
You seems to be using the two-way referencing approach.
The difference between yours and one-to-squillions is you are not only storing post id reference on comment document, but also storing comment ids as reference in post document, while one-to-squillions will only stores project id reference in comment document.
Using your approach will be better if you need to get comment ids of a post. But the disadvantage is you need to run two queries when deleting or creating a comment, one to delete / create comment id from post, and the other one to delete / create the comment document it self. It's also will be slower to find "which post belongs to given comment id".
While using one-to-squillions would gives you worse performance when performing a query to get comments by post id. But you can mitigate this by properly indexing your comment collection.

Aggregate and flatten an array field in MongoDB

I have a Schema:
var ProjectSchema = new Schema({
name: {
type: String,
default: ''
},
topics: [{
type: Schema.ObjectId,
ref: 'Topic'
}],
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
What I want to do is get an array with all topics from all projects. I cannot query Topic directly and get a full list because some topics are unassigned and they do not hold a reference back to a Project (for reasons of avoiding two way references). So I need to query Project and aggregate some how. I am doing something like:
Project.aggregate([{$project:{topics:1}}]);
But this is giving me an array of Project objects with the topics field. What I want is an array with topic objects.
How can I do this?
When dealing with arrays you typically want to use $unwind on the array members first and then $group to find the distinct entries:
Project.aggregate(
[
{ "$unwind": "$topics" },
{ "$group": { "_id": "$topics._id" } }
],
function(err,docs) {
}
)
But for this case, it is probably simplier to just use .distinct() which will do the same as above, but with just an array of results rather than documents:
Project.distinct("topics._id",function(err,topics) {
});
But wait there a minute because I know what you are really asking here. It's not the _id values you want but your Topic data has a property on it like "name".
Since your items are "referenced" and in another collection, you cannot do an aggregation pipeline or .distinct() operation on the property of a document in another collection. Put basically "MongoDB does not perform Joins" and mongoose .populate() is not a join, just something that "emulates" that with additional query(ies).
But you can of course just find the "distinct" values from "Project" and then fetch the information from "Topic". As in:
Project.distinct("topics._id",function(err,topics) {
Topic.find({ "_id": { "$in": topics } },function(err,topics) {
});
});
Which is handy because the .distinct() function already returned an array suitable for use with $in.

Mongoose pre or post save hook for updating the relations?

Given the following Schemata with an n-to-m relation:
var CampaignSchema = new Schema({
name: { type: String },
players: [{
type: Schema.ObjectId,
ref: 'Player'
}]
});
var PlayerSchema = new Schema({
name: { type: String },
campaigns: [{
type: Schema.ObjectId,
ref: 'Campaign'
}]
});
When I create/update a Campaign document, the Player documents should be kept in sync with the players array of the Campaign model.
I saw a lot of examples in the web using the pre save hook, updating the corresponding players to include/exclude this campaign from their campaigns array.
However, I would expect the post save hook to be a better place for this kind of updates, as I can be sure that the model was saved successfully before updating the player documents.
So my question(s) would be:
is there anything I have to consider when choosing between a pre/post save hook to keep my relations in sync, or is it irrelevant and just a matter of taste?
I had the same issue, after implementing a solution myself I ended up with circular dependency issues. Have a look on this package, it solved my problem easily.

Mongoose - Defining child's ObjectID in another Schema

There is a similar thread # Mongoose variable key name. However, he goes with another method instead of solving this. The other method is the OR part of my title.
EDIT - IGNORE THIS SECTION, I AM USING THE ALTERNATIVE NOW. Issue now lays with referencing a child's Object ID elsewhere.
I have the following array:
selections: [{
4433d18d31f3775756ac2a70: "542e91aa31f3775756abccda"},
{4433d18d31f3775756ac2a71: "542e916c31f3775756abccd8"},
{4433d18d31f3775756ac2a72: "542e934231f3775756abccdb"
}]
My schema is currently as follows:
selections: {
<something>: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
In place <something>, is there a way of essentially saying "I don't care what's here"?
ALTERNATIVELY, as this doesn't seem possible after scouring the internet, I can have the following schema:
selections: {
pid: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Competition.CHILD'
}
selection: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
But the issue here is that ObjectID that is being used where <something> is a child schema inside of the schema Competition, and I can't find a way of saying that for the Object ID.
Any advice/help would be great please. Ideally I'd prefer the first solution but I understand it may not be possible. Thanks!
Use an array of objects with optional (but fixed) keys:
selections: [{
selection: {type: ObjectId, ref: 'Selection'},
child: {type: ObjectId, ref: 'Competition.CHILD'}
}]
This will enable you to do better queries, use mongoose population, etc.

How to get and update a specific object inside an array with Mongoose?

I'm having real trouble with MongoDB and Mongoose queries. The thing is, I have this Menu schema:
var menuSchema = new Schema({
userID: String,
name: String,
meals: [{
day: Number,
hour: Number,
minute: Number,
aliments: [{
name: String,
amount: Number,
}],
}],
});
So I'd like to know how to get a specific meal or aliment and how to update them throught the _id value, automatically generated when I add them.
Thank you so much.
David.
I finally could get it working for meals, but not for aliments. I'm doing this for meals:
Menu.update({_id: request.params.menu_id, 'meals._id': request.params.meal_id}, {'meals.$.hour': request.body.hour)}, function(error, affected, raw) {
response.send(error);
});
And this for aliments:
Menu.update({_id: request.params.menu_id, 'meals.aliments._id': request.params.aliment_id}, {'meals.aliments.$': {name: request.body.name, amount: request.body.amount}}, function(error, affected, raw) {
response.send(error);
});
EDIT: ok so I looked for an answer on other posts and I found out the solution.
Looks like the $ positional operator does NOT work with multi-level arrays. Actually, it works only for 1-level arrays (that's why it works for meals but it doesn't for aliments).
So I guess the solution is splitting meals or aliments in documents and reference them with a mealID or alimentID.
I hope they add multi-level positional operator soon. It would be great.

Resources