I want to get the size of array in the model:
const UserSchema = mongoose.Schema(
{
username: { type: String, lowercase: true, required: true },
items: { type: [mongoose.Types.ObjectId], default: [] },
},
{
toJSON : {
virtuals : true
},
timestamps: true,
}
);
UserSchema.virtual("itemsCount").get(function () {
return this.items.length;
});
module.exports = {
UserModel: mongoose.model("user", UserSchema ),
};
const ProductSchema = mongoose.Schema(
{
name: { type: String, required: true },
owner: { type: mongoose.Types.ObjectId,required: true, ref:"user"
},
},
{
toJSON : {
virtuals : true
},
timestamps: true,
}
);
module.exports = {
ProductModel: mongoose.model("product", ProductSchema ),
};
But I want to hide items in the output whenever I try to use projection it gives an error:
Cannot read properties of undefined (reading 'length')
const newPosts = await ProductModel.find({}).populate([{ path: "owner", select: { itemsCount: 0 }}]);
if I don't use select it works:
const newPosts = await ProductModel.find({}).populate([{ path: "owner" }}]);
But I don't want to show items filed in output
You can use aggregation pipeline for this:
ProductModel.aggregate([
{
"$lookup": {
"from": "users",
"localField": "user",
"foreignField": "_id",
"as": "users"
}
},
{
"$unwind": "$users"
},
{
"$project": {
_id: "$_id",
name: "$name",
"users": {
itemsCount: {
$size: "$users.items"
}
}
}
}
])
Read more about $lookup, $unwind, $project to understand.
Here is the Mongodb playground to see the results: https://mongoplayground.net/p/R0ZQiV8I-YM
Related
I am having the following scenario and I need to understand better how population works: I have 3 schema as below. I need to filter child schema based on grandparent property name. I created a populate on pre find but looks like the populate does not help to filter based on property but only on object ID.
The controller code is like below:
filter= {childname: req.params.childname, 'child.parent.grandparent.grandparentname': req.params.grandparent};
const result = await Child.find(filter)
const childSchema = new mongoose.Schema(
{
childname: {
type: String,
},
parent:{
type:mongoose.Schema.ObjectId,
ref: 'Parent',
required: [true, "Parent missing"],
// select: false
}
},
{ timestamps: true },
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
childSchema.pre(/^find/, function(next) {
this.populate
(
{path: 'parent',select: '-__v',
populate: [
{ path: 'grandparent', select: 'grandparentname' }
]
});
next();
});
const Child = mongoose.model( 'Child', childSchema);
module.exports = Child;
const parentSchema = new mongoose.Schema(
{
parentname: {
type: String,
},
grandparent:{
type:mongoose.Schema.ObjectId,
ref: 'grandparent',
required: [true, "Grand parent missing"],
// select: false
},
},
{ timestamps: true },
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
const Parent = mongoose.model( 'Parent', parentSchema);
module.exports = Parent;
const grandparentSchema = new mongoose.Schema(
{
grandparentname: {
type: String,
}
},
{ timestamps: true },
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
const Grandparent = mongoose.model( 'Grandparent', grandparentSchema);
module.exports = Grandparent;
I have the answer!
Firstly - remove your Childschema.pre('^/find...
and use next code:
const result = await Child.aggregate([{
$match: {
childname: YOUR_CHILDNAME_VARIABLE,
}
}, {
$lookup: {
from: 'parents',
localField: 'parent',
foreignField: '_id',
as: 'parent',
pipeline: [
{
"$lookup": {
"from": "grandparents",
localField: "grandparent",
foreignField: "_id",
"as": "grandparent"
},
}, {
$unwind: '$grandparent'
},
]
}
}, {
$unwind: '$parent'
}, {
"$match": {"parent.grandparent.grandparentname": YOUR_GRANDPARENTNAME_VAIRABLE}
}]);
I tried to do it using populate but haven't had a result :(
so, I'v found the solution with aggregate, it works!
I have two collections as follows:
import mongoose from "mongoose";
const projectSchema = mongoose.Schema({
id: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
title: String,
details: String,
location: String,
rate: String,
status: {
type: String,
default: "active",
},
createdAt: {
type: Date,
default: new Date(),
},
});
const Project = mongoose.model("Project", projectSchema);
export default Project;
import mongoose from "mongoose";
const proposalSchema = mongoose.Schema({
id: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
projectId: { type: mongoose.Schema.Types.ObjectId, ref: "Project" },
rate: String,
message: String,
createdAt: {
type: Date,
default: new Date(),
},
});
const Proposal = mongoose.model("Proposal", proposalSchema);
export default Proposal;
And in response to a GET request, I want to get all the projects which are active and user has not sent the proposal to them, GET request will have the id of user.
(Proposal: When a user sends a proposal, a proposal object is created in proposals collections which has userId and ProjectId)
I have make it work using the below queries but it doesn't looks efficient and good. Is there a way I can get this result using aggregate query or any better way from this?
And also how I can efficiently can convert objectId to string Id here.
export const getProjects = async (req, res) => {
try {
const activeProjects = await Project.find({ status: "active" }, { _id: 1 });
const projectsWithProposals = await Proposal.find(
{
$and: [
{ userId: req.query.id },
{ projectId: { $in: activeProjects } },
],
},
{ _id: 0, projectId: 1 }
);
const stringsIds = projectsWithProposals.map((id) =>
id.projectId.toString()
);
const projects = await Project.find({
$and: [{ status: "active" }, { _id: { $nin: stringsIds } }],
});
res.status(200).json(projects);
} catch (error) {
res.status(404).json({ message: error.message });
}
};
Here is a aggregation function which delivers all Projects which have no proposal from a given user:
function getQ (userId) {
return [
{
"$match": {
"$expr": {
"$eq": [
"$status",
"active"
]
}
}
},
{
"$lookup": {
"from": "proposals",
"localField": "_id",
"foreignField": "projectId",
"as": "proposals"
}
},
{
"$set": {
"uids": "$proposals.userId"
}
},
{
"$unset": "proposals"
},
{
"$match": {
"$expr": {
"$not": [
{
"$in": [
mongoose.Types.ObjectId(userId),
"$uids"
]
}
]
}
}
},
{
"$unset": "uids"
},
{
"$limit": 10
}
]
}
db.Project.aggregate(getQ("62a61df204f2ce244ce0ffcc")) // input is user._id
.then(console.log)
.catch(console.error)
I have used the standard mongoose _ids so you might have to adapt the code if required.
The query does only output the Project collection data, although it would be easy to include other data as well.
Beware that limit is constant here. You could also convert skip and limit to function paramters which would make the function much more flexible if you are working with huge amounts of results.
// PRODUCT MODEL
const productSchema = new mongoose.Schema(
{
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
default: 0,
},
colors: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Color",
},
],
sizes: {
type: Array,
default: [
{ id: 1, size: "XS" },
{ id: 2, size: "S" },
{ id: 3, size: "M" },
{ id: 4, size: "L" },
{ id: 5, size: "XL" },
{ id: 6, size: "XXL" },
],
},
},
{ timestamps: true }
);
const Product = mongoose.model("Product", productSchema);
export default Product;
// CATEGORY MODEL
const CategorySchema = new mongoose.Schema(
{
name: String,
},
{ timestamps: true, toJSON: true, toObject: true }
);
CategorySchema.virtual("items").get(async function () {
return await CategorySchema.aggregate([
{
$lookup: {
from: "Product",
localField: "category",
foreignField: "_id",
as: "items",
},
},
]);
});
const Category = mongoose.model("Category", CategorySchema);
export default Category;
Hello, please i'm trying to get list of items that referenced each categoryid on the product model, i want to add the result as a virtual field called "count" from the category model and not the product model. i'm getting this error "TypeError: Cannot use 'in' operator to search for 'transform' in true".
i want to get the result the way it was done in this example from mongodb doc about inventory and orders but i'm getting "items: {}". docs.mongodb.com/manual/reference/operator/aggregation/lookup
router.get(
"/counts",
catchAsync(async (req, res) => {
const stats = await Category.aggregate([
{
$lookup: {
from: "products",
localField: "_id",
foreignField: "category",
as: "count",
},
},
{
$project: {
name: 1,
image: 1,
count: {
$cond: {
if: { $isArray: "$count" },
then: { $size: "$count" },
else: "NA",
},
},
},
},
]);
if (!stats) {
return res.status(404).json("No data found");
}
res.status(200).json(stats);
})
);
For anyone that is trying to achieve the result i wanted, i used Category.aggregate on the category route instead of creating a virtual field from the category model.
I have node text
I have 3 models lead, user and company
I want to get all customers for selected company id using lead model
what I try was,
using $lookup and $group I got unique customer with details but after adding $match I got empty array
I hope someone can get my on the right track..
****This is my Aggregation code ****
const customers = await Lead.aggregate([
{
$match: { moving_company: companyId },
},
{
$group: {
_id: {
"customer": "$customer",
},
}
},
{
$lookup: {
from: "users",
localField: "_id.customer",
foreignField: "_id",
as: "myCustomResut",
},
},
]);
This is my Company model
const schemaOptions: mongoose.SchemaOptions = {
_id: true,
id: false,
timestamps: true,
skipVersioning: true,
strict: false,
toJSON: {
getters: true,
virtuals: true,
},
};
export const CompanySchema = new mongoose.Schema(
{
name: {
type: Schema.Types.String,
},
},
schemaOptions
);
const Company = mongoose.model<ICompany>("Company", CompanySchema);
export default Company;
This my lead model
const schemaOptions: mongoose.SchemaOptions = {
_id: true,
id: false,
timestamps: true,
skipVersioning: true,
strict: false,
toJSON: {
getters: true,
virtuals: true,
},
};
export const LeadSchema = new mongoose.Schema(
{
status: {
type: Schema.Types.String,
},
customer: {
type: Schema.Types.ObjectId,
ref: Customer.modelName
},
assigned_sales_agent: {
type: Schema.Types.String,
ref: Agent.modelName
},
moving_company: {
type: Schema.Types.ObjectId,
ref:Company.modelName
},
selected_categories: [
{
type: SelectedCategorySchema,
},
],
},
schemaOptions
);
const Lead = mongoose.model<ILead>("Lead", LeadSchema);
export default Lead;
This is my customer model
export const customerSchema = new mongoose.Schema(
{
address: {
type: Schema.Types.ObjectId,
ref: Addresses,
},
},
UserSchemaOptions
);
export const Customer = User.discriminator<ICustomer>(
"Customer",
customerSchema,
Role.CUSTOMER
);
export default Customer;
I have a question ...
I have a schema like this :
const chatSchema = new Schema({
[...]
title: {
type: String,
required: true
},
message: [
{
content: {
type: String,
required: true
},
creator: {
type: mongoose.Types.ObjectId,
required: true,
ref: 'User'
}
}
],
[...]
});
in my Node backend, how can I have access to my creators instances inside my message array ? I don’t know how to write the query with populate ...
Thank you all ! :-)
use the following command:
...after importing chatSchema as maybe chats
module.exports.populateCreator = (req, res) => {
chats.findOne({chatID})
.populate({
path: 'message',
populate: {
path: "creator"
}
})
.then(chats => res.json(chats))
}
You can use like this
Chats.find(query)
.populate({
path: 'message',
populate: {
path: 'creator'
}
})
.exec(function(err, docs) {});
the query you are looking for is:
https://mongoplayground.net/p/2dpeZWsXR-V
db.booking.aggregate([
{
"$match": {
id: "61fdfeef678791001880da25"
}
},
{
$unwind: "$cart"
},
{
"$lookup": {
"from": "products",
"localField": "cart.product",
"foreignField": "id",
"as": "prod"
}
},
{
"$unwind": "$prod"
},
{
"$project": {
id: 1,
status: 1,
cart: [
{
id: "$cart.id",
date: "$cart.date",
timeSlots: "$cart.timeSlots",
product: {
id: "$prod.id",
name: "$prod.name",
}
}
],
}
}
])