How to populate 2D array - mongodb

I have a 2D array of games and I want to populate that but It keeps giving me the ID not the Game Object.
const tournamentSchema = new Schema<ITournament>({
...
games: [[{
type: Schema.Types.ObjectId, ref: "Game",
}]],
});
How can I populate games?
Attempts: games.0.$, games, games.0, games.0.0
let populatedGames: IGame[][] = (
await tournament.populate<{ games: IGame[][] }>("<Attempts>")
).games;

Related

Mongoose Many to many relationship with addition field

Hi guys Im trying to create a quiz App, currently I have 2 models:
Quiz model: (which stores all questions, like a category for questions)
import * as mongoose from 'mongoose';
export const QuizSchema = new mongoose.Schema({
name: { type:String, required:true },
slug: { type:String, required:true },
});
Question Model: (which store a single question, with answers etc)
import * as mongoose from 'mongoose';
export const QuestionSchema = new mongoose.Schema({
question: { type:String, required:true },
options: [String],
answers: Array,
quizCategory: [{
type: mongoose.Schema.Types.ObjectId, // here is relate each question to Quiz
ref: "Quiz",
}]
});
So the Problem Im having is this one. I would like to store Question in Quiz Category for that I have added this code:
quizCategory: [{
type: mongoose.Schema.Types.ObjectId, // here is relate each question to Quiz
ref: "Quiz",
}]
For relation, and would allow me to store 1 Question in many Quiz categories, that code above is allowing me that.
But know when I store same Question in different Quiz(categories) I would like to have its position also saved, So for example:
I have 4 Questions: q1,q2,q3,q4 and 2 Quiz categories: 'Cat 1', 'Cat2'...
I would like to save q1 in 'Cat 1' with position 2, and also save that same question in 'Cat 2' with position 5.
And then q2 save in 'Cat 1' on position 4 only, something like this
I would like for each collection of questions I store in Quiz category have order, etc its position. But to be able to reuse 1 Question on few categories with different position, if Im clear :)

How to delete in cascade in several models with mongoose?

I have these 3 models in mongoose:
TravelSchema
var travelSchema = new mongoose.Schema({
name: String,
description: String,
mexican_currency_value: mongoose.Schema.Types.Decimal128
})
travelSchema.pre('deleteOne', function(next) {
const id = this.getQuery()['_id'];
Product.deleteMany({ travel: id }, (err, value) => {
});
next();
});
ProductSchema
var productSchema = new mongoose.Schema({
name: String,
description: String,
purchased_amount: Number,
unit_price_mex: mongoose.Schema.Types.Decimal128,
unit_price_to_sell: mongoose.Schema.Types.Decimal128,
travel: { type: mongoose.Schema.Types.ObjectId, ref: 'Travel' }
})
InvoiceSchema
var invoiceSchema = new mongoose.Schema({
product: productSchema,
client: { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
purchased_amount: Number,
fch: String
});
Where Travel and Product have a one-to-many relationship and Product and Invoice have a one-to-many relationship.
I need the following:
When a Travel is deleted, all Products that are related to that Travel are also deleted.
When these Products are eliminated, all the Invoices related to each Product are also eliminated.
I have managed to eliminate all the products, but when I try to eliminate the invoices I do not obtain the ids of the Products.
invoiceSchema.pre('deleteMany', (next) => {
console.log(this);
// this print { n: 2, ok: 1, deletedCount: 2 }
})
I think you should start the other way when looking at deleting all the related docs. Like you have travel id, with it get all the products and store their id in array and then your first delete should be of the invoices where product._id: { $ in: _arrayOfProducIds }. Once that is complete then deleteMany your products since you already have their ids in the _arrayOfProducIds and lastly deal with the Travel:
travelSchema.pre('deleteOne', function(next) {
const id = this.getQuery()['_id']; // travel id check
// Query to get all the product ids based on the travel id and return array for `$in`
const productIds = this.DoYourQuery // [productIds] check
Invoice.deleteMany({'product._id': { $in: productIds }}, // ... etc
Product.deleteMany({ _id': { $in: productIds }}, // ... etc
next();
});
I would assume you do not have a large number of products and invoices ... like thousands since then $in might be somewhat of a performance issue. Hope this helps.

Relationship field within same collection?

I have a keystonejs model for product categories (MongoDB). Some categories should have subcategories. Currently I have set a relationship field "ChildCategoryOf", where I can manually select the Parent Category in admin panel. To have more functionality, I would like to create another Field called "ParentCategoryOf" that would consist an Array of subcategories. How is it possible to have a field that automatically stores Child categories in Array? I imagine it like this:
Current model:
let ProductCategory = new keystone.List('ProductCategory', {
autokey: {
from: 'name',
path: 'key',
unique: true
}
});
ProductCategory.add({
name: {
type: String,
required: true
},
ChildCategoryOf: {
type: Types.Relationship,
ref: 'ProductCategory',
many: false,
required: false,
},
IsParentCategory: Types.Boolean,
});
For categories in mongo you can use an inheritance like model, in which you can store the parentId and ancestor Id for each model and its children and then add a method to the mongoose model to add a child each time you call it on an instance, this would be like below code:
const ProductCategory = new Schema({
name: String,
parent: { type: Schema.Types.ObjectId, ref: 'ProductCategory'},
ancestors: [{type: Schema.Types.ObjectId, ref: 'ProductCategory'}],
children: [{type: Schema.Types.ObjectId, ref: 'ProductCategory'}]
});
ProductCategory.methods = {
addChild: function(child){
let that = this;
child.parent = this._id;
child.ancestors = this.ancestors.concat([this._id]);
return this.model('ProductCategory').create(child).addCallback
(function(child){
that.children.push(child._id);
that.save();
});
}
};
then later from where you want to find products by their categories you should concat the category id that you find with it's children ids and use this array in find query with $in operator to find all the products in a category with its children.

MongoDB Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.

Mongoose populate() returning empty array

so I've been at it for like 4 hours, read the documentation several times, and still couldn't figure out my problem. I'm trying to do a simple populate() to my model.
I have a User model and Store model. The User has a favoriteStores array which contains the _id of stores. What I'm looking for is that this array will be populated with the Store details.
user.model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var UserSchema = new Schema({
username: String,
name: {first: String, last: String},
favoriteStores: [{type: Schema.Types.ObjectId, ref: 'Store'}],
modifiedOn: {type: Date, default: Date.now},
createdOn: Date,
lastLogin: Date
});
UserSchema.statics.getFavoriteStores = function (userId, callback) {
this
.findById(userId)
.populate('favoriteStores')
.exec(function (err, stores) {
callback(err, stores);
});
}
And another file:
store.model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var StoreSchema = new Schema({
name: String,
route: String,
tagline: String,
logo: String
});
module.exports = mongoose.model('Store', StoreSchema);
After running this what I get is:
{
"_id": "556dc40b44f14c0c252c5604",
"username": "adiv.rulez",
"__v": 0,
"modifiedOn": "2015-06-02T14:56:11.074Z",
"favoriteStores": [],
"name": {
"first": "Adiv",
"last": "Ohayon"
}
}
The favoriteStores is empty, even though when I just do a get of the stores without the populate it does display the _id of the store.
Any help is greatly appreciated! Thanks ;)
UPDATE
After using the deepPopulate plugin it magically fixed it. I guess the problem was with the nesting of the userSchema. Still not sure what the problem was exactly, but at least it's fixed.
I think this issue happens when schemas are defined across multiple files. To solve this, try call populate this way:
.populate({path: 'favoriteStores', model: 'Store'})