Mongoose aggregate and append - node.js

I have a Mongo DB (latest version) that I am accessing with Mongoose (v6.5.4)
The project is using a discriminator pattern to keep all documents in the same collection.
There are many instances where i need to join documents.
Set up:
// Models:
const UserSchema = new Schema<IUser>(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
});
// There are other similar models to <Team>
const TeamSchema = new Schema<ITeam>(
{
name: {
type: String,
required: true,
},
userIds: {
type: [Schema.Types.ObjectId],
required: true,
ref: "User",
default: [],
},
});
Problem:
I can use populate to return collections of Teams and the userIds be an array of user objects.
Where I am stuck is querying getting an array of users with an added field of teams[].
I've been trying aggregate to no success, I can loop over the users collection and return a list of Teams but this feels wrong and expensive in terms of read units (production data base is on a pay as you go service)
As data models go there is not much going for it - but it is an existing solution
Can anyone advise?

I was being stupid. The from field in my look up was wrong.
Should have been 'teams' not 'Team' which is the model name.

Related

Mongoose reference in Collection for another Collection

I'll start with this that I'm new to backend and I was looking for some solutions for my problem but i don't know which solution will be right for my problem.
So to the point. I'm creating a pizza restaurant project in Next.js with Mongoose and simple api. I have for example collections : Product (here will be my pizzas) and the code for this model is below.
import mongoose from "mongoose";
const ProductSchema = new mongoose.Schema
{
title: {
type: String,
required: true,
maxlength: 60,
},
desc: {
type: String,
required: false,
maxlength: 200,
},
img: {
type: String,
required: false,
},
prices: {
type: [Number],
required: true,
},
extraOptions: {
type: [
{
text: { type: String, required: true },
price: { type: Number, required: true },
},
],
},},); export default mongoose.models.Product || mongoose.model("Product", ProductSchema);
In this Schema i have an array of extraOptions ( for example extra cheese for 1$ and extra onion for 1$) but I want to adopt the principle that all products can have the same additional additives. It is a pity to prescribe the same additives for each products ( each pizza)
So, can I create a new model of extraOptions for pizzas and create some 'reference' (like in SQL but in Mongo) for collections Product? For example my simple model of extraOptions :
import mongoose from "mongoose";
const extraSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
maxlength: 60,
},
price: {
type: Number,
required: true,
},
},
);
export default mongoose.models.ExtraOptions || mongoose.model("ExtraOptions", extraSchema);
How could I create a maybe reference in the products collection to display there all documents of the additional extra options ? I was reading about CopyTo method, populate method and subdocuments but i don't know which is solution for me and how can i Use it... Thanks for all answers and sorry if i wrote epic here.
Maybe some extraOptions: [{type:mongoose.Schema.Types.ObjectId,ref:'extraSchema'}],or i dont really know. Thanks a lot for help
It is a pity to prescribe the same additives for each products ( each
pizza) So, can I create a new model of extraOptions for pizzas and
create some 'reference' (like in SQL but in Mongo) for collections
Product?
Your assumption is correct - there is no need to store the same data of additives in each one of your products. Indeed, the right approach is to extract the additives data to a different collection, and reference the relevant additives for each product.
This way, your ProductSchema will look like this (for brevity I excluded the 'required: false' field since false is the default value):
import mongoose from "mongoose";
const ProductSchema = mongoose.Schema({
title: {
type: String,
required: true,
maxlength: 60,
},
desc: {
type: String,
maxlength: 200,
},
img: String,
prices: {
type: [Number],
required: true,
},
extraOptions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'ExtraOptions'
}],
});
export default mongoose.models.Product || mongoose.model("Product", ProductSchema);
In this way, when you get some products documents, the 'extraOptions' property will contain the id's of the extraOptions ducements.
If you want to retrieve the data of the additives itself, instead of just the id, you can use the populate method of mongoose:
Product.find().populate('extraOptions');
For further learning about schema design in mongoDB (it might help you decide in general whether to reference or embed), check this video

How to perform a cascading deleting in MongoDB or Mongoose

I'm working on an e-commerce project in Express and MongoDB. I'm confused with architecture on how to make relationship between two models such that if I delete one element from a table, all of it's associations should be deleted. CASCADE delete, if I'm using the correct term.
I'm not a database designer, just learning Express and MongoDB. So apologies if my schema is not that good.
I have two tables (or documents in MongoDB). One is Order with schema below:
const orderSchema = new mongoose.Schema({
shippingInfo : {
type: mongoose.Types.ObjectId,
ref: 'Address'
},
user : {
type: mongoose.Types.ObjectId,
ref: 'User',
required: true
},
orderItems: [
{
type: mongoose.Types.ObjectId,
ref:'OrderItem'
}
],
totalPrice: {
type: Number,
required: true,
default: 0.0
},
status: {
type: String,
enum: ['processing', 'shipped', 'delivered','cancelled'],
default: 'processing'
},
deliveredAt: {
type: Date,
}
})
and OrderItems
const orderItemSchema = new mongoose.Schema({
product: {
type: mongoose.Types.ObjectId,
ref: 'Product'
},
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
})
I want if I delete an Order, all of its OrderItems should be deleted right away (using remove middleware in Order).
I know that Django has something called on_delete=model.CASCADE when we create relationships, but I'm unaware of such thing in Mongoose.
I don't want to explicitly make another API request to search for and delete all OrderItems that are referenced in orderItems array in an Order, once it is deleted. There has to be a better approach for this.
Another post on Stack Overflow suggested that in remove middleware of Order I should do something like
OrderItem.find({ order_id: this._id }).remove().exec();
That would require me to refer order_id in OrderItem right?
And this would create circular dependency since OrderItem would require Order to be created first and vice versa.
What should I do here? Should I change the schema for both tables i.e. remove orderItems entry from Order and instead add order_id in OrderItem? Or is there a Mongoose way to overcome this situation?

Dont know how to populate Mongoose Query

I have a problem with a mongoose population and I don't know what I should do.
I got two schemas:
var userSchema = new userSchema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
mods: [{ type: mongoose.Schema.Types.ObjectId, ref: 'users'}]
});
var dataSchema = mongoose.Schema({
title: { type: String, required: true, unique: true },
description: { type: String, required: true },
owner: {type: mongoose.Schema.Types.ObjectId, required: true}
});
So one user can have several data packages.
Some users are moderated by other users.
Whats the query for a moderator, that all his own data packages and the ones of the users he is moderating are listed?
You see that I have a SQL background and there's definitely another way to do it with MongoDB.
Thanks for your help!
I'm not clear understand what queries do you need but first you need set ref property in 'owner' field in dataSchema. As about population it's look like this:
//if you use callback
users.find({/*your query*/}).populate('mods')
.exec((err, result)=>{/*your code*/});
//if you use promise
users.find({/*your query*/}).populate('mods').exec()
.then(result=>{/*your code*/})
.catch(err=>{throw err});

MongoDB reusing a collection for every user

I'm not sure the best way to set this up in MongoDB.
I have two collections User and Skill. The collection for Skill has a list of skills that every user should have.
var SkillSchema = new Schema({
name: { type: String, required: true, trim: true },
category: { type: String, required: true, trim: true }
});
mongoose.model('Skill', SkillSchema);
var UserSchema = new Schema({
_id: { type: String, required: true, trim: true },
first_name: { type: String, required: true, trim: true },
last_name: { type: String, required: true, trim: true },
email_address: { type: String, required: true, trim: true },
skills: [{
skill: { type: ObjectId, ref: 'Skill' },
count: { type: Number, default: 0 },
last_performed: { type: Date }
}]
});
mongoose.model('User', UserSchema);
What I am trying to do is have a list of skills, and each user has a count property that shows how many times they have performed the skill, and a last_performed property that has the date they last performed it.
My problem is, I want the list of skills to be same for each user, but I can update their count and last_performed properties uniquely for each user.
The way I have got it currently is referencing the skill id, and then having the count/date in the user schema. The problem with this, is if I add another skill to the Skills schema, the user's skills array won't be updated with the new skill. I figured updating every user every time I add/remove a skill, to reflect the new skills list, wouldn't be the optimal way to do this.
Can you sync the user's skills array to match the Skills schema?
Would it be better to just add each user's count/date to the skill schema directly? The only problem with this is if there are thousands of users, would this have any performance problems, and would it be easy enough to sort the skills by count/date for each user, and query the user's skills individually without returning the counts for every user?
Cheers,
Ben
skills : [{ type: ObjectId, ref: 'Skill' }]. Just push the Ids to the array if you want to add skills when you do a save or an update.
you can populate the array of skills and you can count the skills array. That will give you the count.
If you have last_performed in the Skill model. then you will get access to it after you populate

Get info from 2 separate Mongo documents in one mongoose query

Im using MongoDb, and I have a workspace schema with mongoose (v4.0.1):
var Workspace = new mongoose.Schema({
name: {
type: String,
required: true
},
userId: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
}
});
And a user schema:
var User = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
organisation: {
type: String,
required: true
},
location: {
type: String,
required: true
},
verifyString: {
type: String
},
verified: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
},
isAdmin: {
type: Boolean,
default: false
}
});
So the Workspace userId is the ObjectID from the User document.
When Im logged in as an adminstrator, I want to get all workspaces, as well as the email of the user that owns the workspace.
What Im doing is getting very messy:
Workspace.find({}).exec.then(function(workspaceObects){
var userPromise = workspaceObects.map(function(workspaceObect){
// get the user model with workspaceObect.userId here
});
// somehow combine workspaceObjects and users
});
The above doesnt work and gets extremely messy. Basically I have to loop through the workspaceObjects and go retrieve the user object from the workspace userId. But because its all promises and it becomes very complex and easy to make a mistake.
Is there a much simpler way to do this? In SQL it would require one simple join. Is my schema wrong? Can I get all workspaces and their user owners email in one Mongoose query?
var Workspace = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'User' //add this to your schema
}
});
Workspace.find().populate('userId').exec( (err, res) => {
//you will have res with all user fields
});
http://mongoosejs.com/docs/populate.html
Mongo don't have joins but mongoose provides a very powerfull tool to help you with you have to change the model a little bit and use populate:
Mongoose population
You have to make a few changes to your models and get the info of the user model inside your workspace model.
Hope it helps

Resources