I'm new to Mongoose I don't know how to populate on condition.
So this is my model :
const OrderSchema = new Schema({
products: [{ type: Schema.Types.ObjectId, ref: 'Product' }],
remarks: {type: String, lowercase: true}
});
mongoose.model("Order", OrderSchema);
const ProductSchema = new Schema({
reference: {type: String}
status: {type: Schema.Types.ObjectId, ref: 'ProductStatus'}
});
mongoose.model("Product", ProductSchema);
const ProductStatus = new Schema({
name: {type: String}
});
const CountrySchema = new Schema({
name: {type: String}
});
mongoose.model("Country", CountrySchema);
I have a getOrderById methods
export const getOrderById = async (req, res) => {
let id = req.params.id;
try {
await orderModel
.findById(id)
.populate({
path: 'products',
populate: {
path: 'country',
model: 'Country'
}
})
.populate({
path: 'products',
populate: {
path: 'status',
model: 'ProductStatus'
}
})
.exec(function (err, orders) {
if (err) {
res.send(err);
}
res.status(200).json(orders);
});
} catch (error) {
console.log(error);
}
}
And now I would like to show in the order lists all products that have the status Received in France.
First, I guess you also missed reference to the country in the product schema, so assuming these are your corrected schemas:
const OrderSchema = new Schema({
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}],
remarks: {
type: String,
lowercase: true
}
});
const Order = mongoose.model("Order", OrderSchema);
const ProductSchema = new Schema({
reference: {
type: String
},
country: {
type: Schema.Types.ObjectId,
ref: 'Country'
},
status: {
type: Schema.Types.ObjectId,
ref: 'ProductStatus'
}
});
const Product = mongoose.model("Product", ProductSchema);
const ProductStatusSchema = new Schema({
name: {
type: String
}
});
const ProductStatus = mongoose.model("ProductStatus", ProductStatusSchema);
const CountrySchema = new Schema({
name: {
type: String
}
});
const Country = mongoose.model("Country", CountrySchema);
As far as I understand you want to only show the products, whose country's name is 'France' and ProductStatus' name is 'Received', these kind of operations are done through Aggregation
Your query may look like this assuming you want to do it one query:
const getOrderById = async (req, res) => {
let id = req.params.id.toString();
const ObjectId = mongoose.Types.ObjectId
try {
const aggregationStages = [{
$match: {
_id: ObjectId(id) //It is important to cast to ObjectId in aggregations
}
}, {
$lookup: {
from: 'products',
let: {
productIds: '$products'
},
pipeline: [{
$match: {
$expr: {
$in: ['$_id', '$$productIds']
}
}
}, {
$lookup: {
from: 'countries',
localField: 'country',
foreignField: '_id',
as: 'country'
}
}, {
$lookup: {
from: 'productstatuses',
localField: 'status',
foreignField: '_id',
as: 'status'
}
}, {
$match: {
'country.name': 'France',
'status.name': 'Received'
}
}],
as: 'products'
}
}];
await orderModel.aggregate(aggregationStages)
.exec(function (err, orders) { // The return is an array btw.
if (err) {
res.send(err);
}
res.status(200).json(orders);
});
} catch (error) {
console.log(error);
}
}
If you feel the aggregation is complicated you may resort to breaking it to smaller simpler queries. Feel free to ask if you need more explanation/modification.
Related
I am having a little trouble getting my mongoose virtuals to show up from deep populated fields. Here is the code of the backend function that is not behaving as I'd like it to:
exports.get_user_feed = async (req, res, next) => {
const options = { sort: { date: -1 } };
const user = await User.find(
{ username: req.params.user },
"username posts avatar followers following"
)
.populate({
path: "posts",
options,
populate: [
{
path: "author",
},
{ path: "comments", populate: { path: "author" } },
],
})
.sort({ "posts.date": 1 });
res.json({ ...user });
};
And here is the comment schema:
const mongoose = require("mongoose");
const { DateTime } = require("luxon");
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
targetPost: { type: Schema.Types.ObjectId, ref: "Post", required: true },
author: { type: Schema.Types.ObjectId, ref: "User", required: true },
date: { type: Date, required: true },
content: { type: String, maxlength: 400 },
comments: [{ type: Schema.Types.ObjectId, ref: "Comment" }],
stars: [{ type: Schema.Types.ObjectId, ref: "User" }],
});
// Virtual for post's URL
CommentSchema.virtual("url").get(function () {
return "/" + this.targetPost.url + this._id;
});
// Virtual for formatted date.
CommentSchema.virtual("formatted_date").get(function () {
return (
DateTime.fromJSDate(this.date).toLocaleString(DateTime.DATE_MED) +
" at " +
DateTime.fromJSDate(this.date).toLocaleString(DateTime.TIME_SIMPLE)
);
});
//Export model
module.exports = mongoose.model("Comment", CommentSchema);
My goal is to get the comments from each post to also include the formatted_date of the comment, but this virtual is not getting included in the response that is sent - all the regular properties are being sent but not the virtual. Any help here would be appreciated.
Add this code in your Commnet Schema file before module.exports.
CommentSchema.method('toJSON', function () {
const {
...object
} = this.toObject({ virtuals:true });
return object;
});
I want to be able to delete comment that is inside my Post model.
This is my Schema for Post model:
const PostSchema = new Schema({
userID: {
type: Schema.Types.ObjectId,
ref: 'user'
},
content: {
type: String,
required: true
},
registration_date: {
type: Date,
default: Date.now
},
likes: [
{
type: Schema.Types.ObjectId,
ref: "user"
}
],
comments: [
{
text: String,
userID: {
type: Schema.Types.ObjectId,
ref: 'user'
}
}
]
})
And I have this route:
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
const postId = req.params.id
const commentId = req.params.comment_id
}
comments in post looks like this:
comments: [
{
_id: 5f1df4cf5fd7d83ec0a8afd8,
text: 'comment 1',
userID: 5efb2296ca33ba3d981398ff
},
{
_id: 5f1df4d35fd7d83ec0a8afd9,
text: 'commnet 2',
userID: 5efb2296ca33ba3d981398ff
}
]
I want to delete comment, and don't know how to do it. Does anyone have idea how to do it?
First we find the post by findByIdAndUpdate then we delete the comment using $pull from the array of comments.
router.delete("/comment/:id/:comment_/id", async function (req, res) {
try {
const post = await Post.findByIdAndUpdate(
req.params.id,
{
$pull: { comments: {_id:req.params.comment_id}},
},
{ new: true }
);
if (!post) {
return res.status(400).send("Post not found");
}
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});
My delete route is
const id = req.body.id;
const postId = req.body.postId;
if (mongoose.Types.ObjectId.isValid(id)) {
Comment.findByIdAndRemove({ _id: id }, (err, cRes) => {
if (err) return err;
Post.findOneAndUpdate({ _id: postId }, {
$pull: {
Comments: {
_id: id
}
}
}, (err, doc, res) => {
if (err) console.log(err);
res.redirect(req.get('referer'));
});
});
}
And the problem is that it does delete the comment from the Comment table but it doesn't delete the comment to the related Post, why is that?
PostSchema
var PostSchema = new mongoose.Schema({
Author: String,
Title: String,
Description: String,
Comments: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Comment'
}],
Tags: [{
type: mongoose.Schema.Types.String, ref: 'Tag'
}],
CreatedOn: Date,
LastEditOn: Date
});
CommentSchema
var CommentSchema = new mongoose.Schema({
_postId: {
type: String,
ref: 'Post'
},
Author: String,
Description: String,
CreatedOn: Date,
LastEditBy: Date
});
no need to put _id during pull because you haven't mentioned any key in Comments of Post collection.
if (mongoose.Types.ObjectId.isValid(id)) {
Comment.findByIdAndRemove({ _id: id }, (err, cRes) => {
if (err) return err;
Post.update({ _id: postId }, {
$pull: {
Comments: id
}
}, (err, doc, res) => {
if (err) console.log(err);
res.redirect(req.get('referer'));
});
});
}
If you define _id in post schema like
Comments: [{
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }
}]
then your query could have worked.
The purpose of this is to fetch A pro of Type Secretaire from a Cabinet with a specified name (in this case "Clinique Toto") and I'm struggling here.
Cab Model:
var cabinet = new cabModel({
_id: new mongoose.Types.ObjectId(),
InfoCab:{Nom: "Clinique Toto"} //This is the Name of the Cabinet
});
cabinet.save((err, cabinet) => {
Pro Model
var pro1 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'ProTITI',
Cv:{ Fonction: { Secretaire: false}}
});
pro1.Cabinets.push(cabinet._id);
pro1.save((err, cabinet) => { });
var pro2 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'Pro_TOT',
Cv:{ Fonction: { Secretaire: true}}
});
Setting Secretaire: true for some of the Pros.
pro2.Cabinets.push(cabinet._id);
pro2.save((err, cabinet) => { });
var pro3 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'Josianne',
Cv:{ Fonction: { Secretaire: true}}
});
pro3.Cabinets.push(cabinet._id);
pro3.save((err, cabinet) => { });
Pushing Pros created into the Cab.
cabinet.Pro.push(pro1, pro2, pro3);
cabinet.save();
console.log("Done");
});
const handleError = function (err) {
console.error(err);
};
I got to this so far:
db.Pro.aggregate([
{
$match: {
Cv: {
Fonction: {
Secretaire: true
}
}
}
},
{
$lookup:
{
from: "Cab",
localField:"Nom",
foreignField: "_id",
as: "PK"
}
}
])
Here are the Schemas:
Pro Schema:
const ProSchema = new Schema({
_id: { type: Schema.Types.ObjectId },
Cv: {Fonction: {Pro: {type: Boolean,},
Secretaire: {type: Boolean}
}
}
CabSchema:
const CabSchema = new Schema({
Pro: [{ type: Schema.Types.ObjectId, ref: 'ProSchema' }],
InfoCab: {
Nom: {type: String}
});
Can you add Schema for your models so your question has more clarification.
From the given information, it looks like Nom is a string, i.e. Nom: 'Josianne' and you are using lookup as follows:
$lookup:
{
from: "Cab",
localField:"Nom",
foreignField: "_id",
as: "PK"
}
Now the problem is _id is of type ObjectId(), which is a hash string uniquely generated, where Nom is a string created by you, logically they will never match.
iF Cab collection is same as cabModel, the foreignField should be InfoCab.Nom. i.e. foreignField: "InfoCab.Nom",
=== UPDATE ===
Couple of observations you might wana consider:
you should use the aggregation as following: proModel.aggregate([...])
If you are already using ref in your mongoose schema, you can use .populate() method.
I try to play with populate but without success ...
It's possible to do this?
I have 2 shema :
- User
import mongoose, { Schema } from 'mongoose'
const userSchema = new Schema({
email: { type: String, unique: true },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
products: [{
productId: { type: Schema.Types.ObjectId, ref: 'Product' },
dateAdd: { type: Date, default: Date.now }
}]
}, { timestamps: true })
const User = mongoose.model('User', userSchema)
export default User
And Product :
import mongoose, { Schema } from 'mongoose'
const productSchema = new Schema({
domain: String,
originUrl: { type: String },
price: Number,
img: String,
userFollow: [{ type: Schema.Types.ObjectId, ref: 'User' }]
})
const Product = mongoose.model('Product', productSchema)
export default Product
So I want to retrieve all the info for each of my prodcutId
I try this way (and many others without success):
User.findOne({ _id: userId }).populate({
path: 'products.productId',
populate: { path: 'products.productId', model: 'Products' }
}).exec(function (err, products) {
if (err) {
console.log('errors :' + err)
}
console.log('Product => ' + util.inspect(products))
})
Populate has no effect, same result with just the findOne()
I think User.findOne({ _id: userId }).populate('products.productId') should work.
Try using aggregate function of MongoDB and $lookup.
Users.aggregate([
{
"$match":
{
_id: user.id
}
},
{
"$lookup":
{
from: "Product",
localField: "products",
foreignField: "_id",
as: "products"
}
}
])
.exec()
.then((result) => {
//your result
})
.catch((err) => {
// if any error
});