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.
Related
I have already checked the other entries on StackOverflow, but it did not help.
I am building a RESTapi with node.js, and I am using MongoDB with mongoose
I have a Schema that contains three different models. I am able to save POST request to the entry. I am sure that entry is saved because I checked on atlas.mongo. However, I have a problem when I am trying to use GET request.
It gives this error:
Cast to ObjectId failed for value "" at path "_id" for model
These are my Models: (These models are in different files)
const Model1 = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
word1: { type: [String], require: true }
});
----------------------------------------------
const Model2 = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
word2: { type: [String], require: true }
});
----------------------------------------------
const Model3 = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
element1: { type: [String], default: ""},
element2: { type: [String], default: ""}
});
----------------------------------------------
const Word = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
md3: { type: mongoose.Schema.Types.Mixed, ref: 'Model3', require: true },
md2: { type: mongoose.Schema.Types.Mixed, ref: 'Model2', require: true },
md1: { type: mongoose.Schema.Types.Mixed, ref: 'Model1', require: true }
});
This is my POST request:
exports.entry_create = (req, res, next) => {
const newModel3 = new Model3({
_id: new mongoose.Types.ObjectId(),
element1: req.body.element1,
element2: req.body.element2
});
const newModel2 = new Model2({
_id: new mongoose.Types.ObjectId(),
word2: req.body.word2
});
const newModel1 = new Model1({
_id: new mongoose.Types.ObjectId(),
word1: req.body.word1
});
const newEntry = new Word({
_id: new mongoose.Types.ObjectId(),
md3: newModel3,
md2: newModel2,
md1: newModel1
});
newEntry
.save(); // i have also then() and catch() part
};
This is where I got the error on Postman
exports.entry_get_all = (req, res, next) => {
Word.find()
.select('_id md3 md2 md1')
.populate('md3')
.populate('md2')
.populate('md1')
.exec()
.then(docs => {
res.status(200).json({
numOfEntries: docs.length,
Entries: docs.map(doc => {
return {
_id: doc._id,
md3: doc.md3,
md2: doc.md2,
md1: doc.md1,
request: { type: 'GET' }
}
})
});
}); // i have also catch() part
};
What could be the problem? Is _id's of md3, md2 & md1 returns null?
I believe it has to do with your references md1, md2 and md3. The way you reference another model is by the _id, which in your case it's and ObjectId. That being said, when you define md1, md2, and md3 you say the type is mixed, not an ObjectId. Do this instead:
const Word = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
md3: { type: mongoose.Schema.Types.ObjectId, ref: 'Model3', require: true },
md2: { type: mongoose.Schema.Types.ObjectId, ref: 'Model2', require: true },
md1: { type: mongoose.Schema.Types.ObjectId, ref: 'Model1', require: true }
});
Also note: You don't need to explicitly create a new ObjectId when creating an instance of your model. If using mongoose, it creates the _id for you! So you can just create a new Word like this:
let md1 = null;
let md2 = null;
let md3 = null;
const newModel3 = new Model3({
element1: req.body.element1,
element2: req.body.element2
});
// Save newModel3
newModel3.save()
.then((_md3) => {
md3 = _md3;
const newModel2 = new Model2({
word2: req.body.word2
});
return newModel2.save();
})
.then((_md2) => {
md2 = _md2;
const newModel1 = new Model1({
word1: req.body.word1
});
return newModel1.save();
})
.then((_md1) => {
md1 = _md1
const newEntry = new Word({
md3: md3._id,
md2: md2._id,
md1: md1._id
});
return newEntry.save();
})
I have a Person schema :
const person = new mongoose.Schema({
name: String,
birthday: Date,
sex: String
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
person.virtual('tasks', {
ref: 'task',
localField: '_id',
foreignField: 'person'
});
export default mongoose.model('person', person);
And a task one which has the person field :
const schema = new mongoose.Schema({
person: {
type: mongoose.Schema.Types.ObjectId,
ref: 'person',
required: true
},
name: String
});
export default mongoose.model('task', schema);
And my API to retrieve one person :
api.get('/person/:id', async (req, res) => {
let personID = req.params.id;
if (personID) {
let person = await Person.findById(personID);
if (person)
res.json(person);
else
res.status(404).end();
}
else {
res.status(400).end();
}
});
When I query the API for one person, tasks is always null, any ideas why?
Thanks
Update
I tried with :
let person = await Person.findById(personID).populate('tasks').exec();
and it's still returning null.
Use populate in your query.
Can you please check one of the below query
let person = await Person.findOne({ _id: personID })
.populate('tasks')
.exec((error, personData)
=> personData);
OR
let person = await Person.findById(personID)
.populate('tasks')
.exec((error, personData) => personData);
reference Link : http://thecodebarbarian.com/mongoose-virtual-populate
Ok I found the issue, I was registering the model before setting up the virtual (in some wrapper I wrote around mongoose.Schema).
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.
Here is the 1st Schema:
const ProSchema = new Schema({
_id: { type: Schema.Types.ObjectId },
Cv: {Fonction: {Pro: {type: Boolean,},
Secretaire: {type: Boolean}
}
})
Here's the second Schema:
const CabSchema = new Schema({
Pro: [{ type: Schema.Types.ObjectId, ref: 'ProSchema' }],
InfoCab: {
Nom: {type: String}
});
Creating a cabinet:
var cabinet = new cabModel({
_id: new mongoose.Types.ObjectId(),
InfoCab:{Nom: "Clinique Toto"} //This is the Name of the Cabinet
});
cabinet.save((err, cabinet) => {
Creating pro1:
var pro1 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'ProTITI',
Cv:{ Fonction: { Secretaire: false}}
});
pro1.Cabinets.push(cabinet._id);
pro1.save((err, cabinet) => { });
Creating pro2:
var pro2 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'Pro_TOT',
Cv:{ Fonction: { Secretaire: true}}
});
pro2.Cabinets.push(cabinet._id);
pro2.save((err, cabinet) => { });
Creating Pro3:
var pro3 = new proModel({
_id: new mongoose.Types.ObjectId(),
Nom: 'Josianne',
Cv:{ Fonction: { Secretaire: true}}
});
pro3.Cabinets.push(cabinet._id);
pro3.save((err, cabinet) => { });
cabinet.Pro.push(pro1, pro2, pro3);
cabinet.save();
The purpose of this question is the following:
How can fetch a Pro of type Secretaire from Clinique Toto.
So far I tried with this:
db.Pro.aggregate([
{
$match: {
Cv: {
Fonction: {
Secretaire: true
}
}
}
},
{
$lookup:
{
from: "Cab",
localField:"InfoCab.Nom",
foreignField: "_id",
as: "PK"
}
}
But I'm stuck with the last bit: from Clinique Toto.
Cheers,
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
});