.aggregate() on 2 collections mongoose - node.js

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,

Related

autopopulate & virtual in child schema does not work

I have the following schemas:
"use strict";
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
autopopulate = require('mongoose-autopopulate');
const child = new Schema({
userUuid: {
type: String,
required: true
},
timeStamp: {
type: Date,
default: new Date()
}
}, {toJSON: {virtuals: true}});
child.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
const parentList= new Schema({
//some properties
children: [child]
});
parentList.plugin(autopopulate);
module.exports = parentList;
I need the Children's list to extract a full object - but it does not work.
When I put a single user not as a child then it works well:
const try= new Schema({
//some properties
userUuid: {
type: String,
required: true
}
}, {toJSON: {virtuals: true}});
try.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
try.plugin(autopopulate);
module.exports = try;
This leads me to the conclusion that the problem is when the virtual is within the child schema
What am I missing?
Here's the full code of my attempt to reproduce yours :
const
{ randomUUID } = require('node:crypto'),
{ MongoMemoryServer } = require('mongodb-memory-server'),
mongoose = require('mongoose'),
{ Schema } = mongoose,
autopopulate = require('mongoose-autopopulate');
(async () => {
const
dbServer = await MongoMemoryServer.create(),
dbClient = mongoose.createConnection(dbServer.getUri());
dbClient.on('disconnected', () => dbServer.stop());
await new Promise(resolve => dbClient.once('connected', () => resolve()));
try {
const trySchema = new Schema({
//some properties
userUuid: {
type: String,
required: true
}
}, { toJSON: { virtuals: true } });
trySchema.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
trySchema.plugin(autopopulate);
const childSchema = new Schema({
userUuid: {
type: String,
required: true
},
timeStamp: {
type: Date,
default: new Date()
}
}, { toJSON: { virtuals: true } });
childSchema.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
childSchema.plugin(autopopulate);
const parentListSchema = new Schema({
//some properties
children: [childSchema]
});
parentListSchema.plugin(autopopulate);
const userSchema = new Schema({
uuid: {
type: String,
required: true
}
});
const
Try = dbClient.model('try', trySchema),
Child = dbClient.model('child', childSchema),
ParentList = dbClient.model('parentList', parentListSchema),
User = dbClient.model('users', userSchema);
const userUuid = randomUUID();
await new User({ uuid: userUuid }).save();
await new Try({ userUuid }).save();
const child = await new Child({ userUuid }).save();
await new ParentList({ children: [child] }).save();
console.log('User:', (await User.findOne().exec()).toJSON());
console.log('Try:', (await Try.findOne().exec()).toJSON());
console.log('Child:', (await Child.findOne().exec()).toJSON());
console.log('ParentList:', (await ParentList.findOne().exec()).toJSON());
}
catch(error){
console.error(error);
}
dbClient.close();
})();
Which outputs :
User: {
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
Try: {
_id: new ObjectId("62c6e7bcef50638fe0097868"),
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0,
user: [
{
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
],
id: '62c6e7bcef50638fe0097868'
}
Child: {
_id: new ObjectId("62c6e7bcef50638fe009786b"),
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
timeStamp: 2022-07-07T14:03:40.902Z,
__v: 0,
user: [
{
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
],
id: '62c6e7bcef50638fe009786b'
}
ParentList: {
_id: new ObjectId("62c6e7bcef50638fe009786e"),
children: [
{
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
timeStamp: 2022-07-07T14:03:40.902Z,
_id: new ObjectId("62c6e7bcef50638fe009786b"),
__v: 0,
id: '62c6e7bcef50638fe009786b'
}
],
__v: 0
}
I'm not sure what you meant about [needing] the Children's list to extract a full object.
If you were talking about Child.user then you were only missing child.plugin(autopopulate);.
If you were talking about ParentList.children then it worked out of the box for me.

Graphql express nested schema

I am currently building mern + graphql app. I am new to graphql and I have two different models a User Model and Character model. The Character Model is nested in the User. When I create a user in the Graphiql interface I want it to display its data and the character that is associated with the specific user but instead it is displaying every character that has been created in the database and adding them to every user created instead of the specific user. Trying to figure out how to fix this. Link to Github Repo
Mongoose User Model
const mongoose = require('../db/connection')
const Schema = mongoose.Schema
const User = new Schema({
id: String,
name: String,
totalwins: Number,
description: String,
charactersID: [
{
type: Schema.Types.ObjectId,
ref: "Character"
}
]
})
module.exports = mongoose.model('User', User)
Mongoose Character Model
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const Character = new Schema({
name: String,
wins: Number,
losses: Number,
percentage: Number,
image: String
})
module.exports = mongoose.model('Character', Character)
const graphql = require('graphql')
const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList,
GraphQLNonNull } = graphql
const Character = require("../models/Character")
const User = require("../models/User")
Types
// User Schema
const userType = new GraphQLObjectType({
name: 'User',
fields : () => ({
_id: {type: GraphQLID},
name : { type: GraphQLString},
totalwins : { type: GraphQLInt },
description : { type: GraphQLString },
character : {
type : new GraphQLList(characterType),
resolve(parent, args){
return Character.find({charactersID: parent.id})
}
}
})
})
// Character Schema
const characterType = new GraphQLObjectType({
name: 'Character',
fields : () => ({
id: {type: GraphQLID},
name : { type: GraphQLString},
wins : { type: GraphQLInt },
losses : { type: GraphQLInt },
percentage: {type: (GraphQLInt)},
image: { type: GraphQLString },
})
})
Query
//query the graph to grab the data
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
user : {
type: userType,
// arguments passed by the user while making the query
args: {id : {type : GraphQLID}},
resolve(parent, args){
// return User.find((item) => { return item.id == args.id});
//finding a single user by id
return User.findById(args.id)
}
},
users : {
type: new GraphQLList(userType),
resolve(parent, args) {
return User.find({})
}
},
character : {
type: characterType,
args: {id : {type : GraphQLID}},
resolve(parent, args){
return Character.findById(args.id)
}
},
characters : {
type: new GraphQLList(characterType),
resolve(parent, args) {
return Character.find({})
}
}
}
})
Mutations
//allows user to add, update and delete to mondodb through graphql
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addUser: {
type: userType,
args: {
name: {type: GraphQLNonNull(GraphQLString)},
totalwins: {type: (GraphQLInt)},
description: {type: (GraphQLString)},
charactersID: { type: new GraphQLNonNull(GraphQLID)}
},
resolve(parent, args){
let user = new User({
name: args.name,
totalwins: args.totalwins,
description: args.description,
charactersID: args.charactersID
})
return user.save()
}
},
addCharacter: {
type: characterType,
args: {
name: {type: GraphQLNonNull(GraphQLString)},
wins: {type: (GraphQLInt)},
losses: {type: (GraphQLInt)},
percentage: {type: (GraphQLInt)},
image: {type: (GraphQLString)},
},
resolve(parent, args){
let character = new Character({
name: args.name,
wins: args.wins,
losses: args.losses,
percentage: args.percentage,
image: args.image
})
return character.save()
}
},
deleteUser: {
type: userType,
args: {
id: {type: new GraphQLNonNull(GraphQLString)}
},
resolve(parent, args){
const remUser = User.findByIdAndRemove(args.id)
if(!remUser){
throw new Error('No character found')
}
return remUser
}
},
deleteCharacter: {
type: characterType,
args: {
id: {type: new GraphQLNonNull(GraphQLString)}
},
resolve(parent, args){
const remCharacter = Character.findByIdAndRemove(args.id)
if(!remCharacter){
throw new Error('No character found')
}
return remCharacter
}
},
}
})
module.exports = new GraphQLSchema({ query : Query, mutation : Mutation})
Image of results from Grapiql interface

Cast to ObjectId failed for value "" at path "_id" for model

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();
})

Mongoose - How to populate a sub document on condition

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.

Aggregation issue with 2 collections

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.

Resources