How do I find object based on child populated property on mongoose - node.js

I'm new in mongoose.
I have a Schema like this:
const sessionSchema = mongoose.Schema({
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
registers: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Register',
},
],
date: {
type: Date,
default: Date.now,
immutable: true,
},
});
And register model is this one:
const registerSchema = mongoose.Schema({
sets: [
{
weight: {
type: Number,
},
weightUnit: {
type: String,
default: 'kg',
},
repetitions: {
type: Number,
},
duration: {
type: Number,
},
},
],
session: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Session',
},
exercise: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Exercise',
},
creationDate: {
type: Date,
default: Date.now,
immutable: true,
},
});
I want to find all the sessions created by a user and has an register with an specificied exercise id. I tried this:
const result = Session.find({createdBy: userId, 'registers.exercise': exerciseId}).populate('registers');
But doesn't work. ¿Any suggestion?
Thanks :P

Try this:
Session.aggregate([
{
$match: { createdBy: userId }
},
{
$lookup: {
from: 'Register',
localField: 'registers',
foreignField: '_id',
as: 'registers'
}
},
{
$match: { 'registers.exercise': exerciseId }
}
])
.then((result) => {
console.log('Result: ', result);
})
.catch((err) => {
console.log('Error: ', err);
});
If you are not familiar with MongoDB aggregation framework, the query above might be a bit foreign to you. However, for cross collection queries like you described in your question, the aggregation framework is your best shot(for now).
The $match is basically doing what you would do with a Model.find(), while the $lookup is doing what you would do with a Model.<query>.populate()
You can read more about MongoDB aggregation framework here.

Related

Mongoose search query with regex not returning expected results with multiple conditions and population

export const searchPost = async (req: any, res: Response) => {
try {
const searchQuery = req.params.query;
const page = req.query.page || 1;
const limit = req.query.limit || 10;
const skip = (page - 1) * limit;
const posts = await Post.find({
$or:
[
{ content: { $regex: searchQuery, $options: 'i' } },
{ location: { $regex: searchQuery, $options: 'i' } },
{ 'user.fullName': { $regex: searchQuery, $options: 'i' } },
{ 'user.username': { $regex: searchQuery, $options: 'i' } },
{ 'group.name': { $regex: searchQuery, $options: 'i' } }
]
})
.populate('user')
.populate('group')
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 });
res.json(posts);
} catch (err) {
console.log(err);
return res.status(500).json({ message: 'Something went wrong!' });
}
};
Post Modal
const PostSchema = new Schema(
{
content:
{
type: String,
required: true
},
location:
{
type: String
},
image:
{
type: String
},
user:
{
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
group:
{
type: Schema.Types.ObjectId,
ref: 'Group'
},
comments:
[
{
type: Schema.Types.ObjectId,
ref: 'Comment'
}
],
likesCount:
{
type: Number,
default: 0
},
likesUsers:
[
{
type: Schema.Types.ObjectId,
ref: 'User'
}
]
},
{ timestamps: true }
);
const Post = mongoose.model('Post', PostSchema);
export default Post;
User Model
const UserSchema = new Schema(
{
fullName:
{
type: String,
required: true,
index: true
},
username:
{
type: String,
required: true,
index: true
},
email:
{
type: String,
required: true,
unique: true
},
password:
{
type: String,
required: true
},
profileImg:
{
type: String,
default:
'https://res.cloudinary.com/dyfm31f1n/image/upload/v1675059905/fit-fiesta/placeholders/blank-profile-picture-gdb207bae8_1280_zymz7e.png'
},
coverImg:
{
type: String,
default:
'https://res.cloudinary.com/dyfm31f1n/image/upload/v1675059731/fit-fiesta/placeholders/bg_qr4vtm.jpg'
},
location:
{
type: String
},
weight:
{
type: Number
},
height:
{
type: Number
},
targetWeight:
{
type: Number
},
groups:
[
{
type: Schema.Types.ObjectId,
ref: 'Group'
}
],
events:
[
{
type: Schema.Types.ObjectId,
ref: 'Event'
}
],
posts:
[
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
],
connections:
[
{
type: Schema.Types.ObjectId,
ref: 'User'
}
],
pendingConnections:
[
{
type: Schema.Types.ObjectId,
ref: 'User'
}
]
},
{ timestamps: true }
);
The Post model has a reference to a User model and a Group model. The method populates these references with their corresponding data using the populate method. The result of the query is sorted in descending order of creation time and sent as a response to the client.
Here in searchPost API its not considering user relation fields such as user.username and user.fullName or group.name while finding with regex
The find, skip, limit, and sort are performed by the mongodb database on the server side. The result is then sent back to mongoose where the populate is performed by submitting additional queries.
In the 'post' document in the database, the 'user' and 'group' fields contains an ObjectId, not an object, so the fields `user.fullName', 'user.username', and 'group.name' don't exist, and therefore don't match.
In order to filter these fields on the database server, you would need to use aggregate with separate $lookup stages to retrieve the user and group documents in order for the server to consider those fields.

How to find sum in Mongo aggregate method with match in different collection in an array of object?

I am trying to find the sum by matching array of object using mongoose. I have 2 collection such as
const accountSchema = new mongoose.Schema({
groupId: {
type: Number,
required: true
},
account_no: {
type: String,
required: true
},
account_name: {
type: String,
required: true
},
opening_balance: {
type: Number,
default: 0
}
})
And second collection as:
const mongoose = require('mongoose')
const AutoIncrement = require('mongoose-sequence')(mongoose);
const accountJournalSchema = new mongoose.Schema({
journal_no: {
type: Number
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users',
required: [true, 'User ID is required.'],
},
groupId: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
receipt: [
{
account_no: {
type: mongoose.Schema.ObjectId,
ref: 'Accounts',
required: true
},
debit: {
type: Number,
default: 0
},
credit: {
type: Number,
default: 0
},
}
]
})
And my aggregate method is:
await Accounts.aggregate([
{
$match: {
$and: [
{ groupId: {$eq: parseInt(req.params.group_id)} },
{ 'Account_jour.groupId': { $eq: parseInt(req.params.group_id) } }
]
}
},
{ unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])
I am getting error from the above statement:
Arguments must be aggregate pipeline operators
And after solving the issue I also want to find the sum of debit and credit.
Thank you!!
Try this:
await Accounts.aggregate([
{
$match: {
$and: [{ groupId: { $eq: parseInt(req.params.group_id) } }, { "Account_jour.groupId": { $eq: parseInt(req.params.group_id) } }]
}
},
{ $unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])

Populating Virtual Field in Mongoose

I'm currently building an application that logs poker statistics. Users create players and then populate games with those players.
I have a games model which references the players from the player model:
const gameSchema = new mongoose.Schema({
firstPlace: { type: mongoose.Schema.ObjectId, ref: 'Players', required: true },
secondPlace: { type: mongoose.Schema.ObjectId,ref: 'Players', required: true },
thirdPlace: { type: mongoose.Schema.ObjectId, ref: 'Players', required: true },
fourthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
fifthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
sixthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
seventhPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
eighthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
ninthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
buyIn: { type: Number, required: true },
firstPrize: { type: Number, required: true },
secondPrize: { type: Number, required: true },
thirdPrize: { type: Number, required: true },
date: { type: String },
notes: { type: String },
userId: { type: mongoose.Schema.ObjectId, ref: 'Users', required: true },
})
To get the information on a single game, I populate the references to players -
// Get single Game
async function getSingleGame(req, res, next) {
try {
const { gameId } = req.params
const foundGame = await Games.findById(gameId)
.populate('userId')
.populate('firstPlace')
.populate('secondPlace')
.populate('thirdPlace')
.populate('fourthPlace')
.populate('fifthPlace')
.populate('sixthPlace')
.populate('seventhPlace')
.populate('eighthPlace')
.populate('ninthPlace')
if (!foundGame) throw new NotFound()
return res.status(200).json(foundGame)
} catch (err) {
next(err)
}
}
This works well, so for example firstPlace is populated with an object that contains all the information of that player.
However, I have a virtual field on my user model which gets all the games that user has created. Code below -
userSchema
.virtual('addedGames', {
ref: 'Games',
localField: '_id',
foreignField: 'userId',
})
.get(function (addedGames) {
if (!addedGames) return
return addedGames.map((game) => {
return {
_id: game._id,
firstPlace: game.firstPlace,
secondPlace: game.secondPlace,
thirdPlace: game.thirdPlace,
fourthPlace: game.fourthPlace,
fifthPlace: game.fifthPlace,
sixthPlace: game.sixthPlace,
seventhPlace: game.seventhPlace,
eigthPlace: game.eigthPlace,
ninthPlace: game.ninthPlace,
buyIn: game.buyIn,
firstPrize: game.firstPrize,
secondPrize: game.secondPrize,
thirdPrize: game.thirdPrize,
}
})
})
This works - but all the placings are objectIds rather than the objects themselves. My question is - how can I populate these references to Players on this virtual field?
Thanks in advance, I've been pulling my hair out over this!
After much trial and error, I added this to populate the nested objects -
let user = await User.findById(currentUserId)
.populate('addedPlayers')
.populate([
{
path: 'addedGames',
populate: {
path: 'firstPlace secondPlace thirdPlace fourthPlace fifthPlace sixthPlace seventhPlace eighthPlace',
},
},
])
Originally I just had populate('addedGames')

mongoose aggregation not returning expected result

I am currently learning aggregation and some related methods, after following mongoose and mongodb docs i tried it. but i am having problem.
Follow.aggregate([
{ $match: { user: mongoose.Types.ObjectId(userId) } },
{ $unwind: '$followers' },
{
$lookup: {
from: 'accounts',
localField: 'followers',
foreignField: '_id',
as: 'followers'
}
},
{ $project: { name: 1, photo: 1 } },
]).exec((err, followers) => {
if (err) throw err;
console.log(followers);
res.send(followers);
});
I want to get the followers of that userID and select the followers names and photo, but i am only getting the objectid of the matched document
[ { _id: 5bfe2c529419a560fb3e92eb } ]
expected output
[ { _id: 5bfe2c529419a560fb3e92eb , name: 'john doe", photo: 'cat.jpg'} ]
Follow Model
const FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
},
following: [{
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
}],
followers: [{
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
}],
});
When the $project stage is wrong you would get that result. Try removing it first and see the output. To me it seems you should have something like this there:
{ $project: { "followers.name": 1, "followers.photo": 1 } },

Accessing object in array of arrays using mongoose

I have the following structure and am trying to remove an object in participants (league.division.participants).
var participantSchema = new mongoose.Schema({
player: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
record: { type: mongoose.Schema.Types.ObjectId, ref: 'ParticipantRecord' },
events: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Event' } ]
});
var divisionSchema = new mongoose.Schema({
name: String,
participants: [ participantSchema ]
});
var leagueSchema = new mongoose.Schema({
name: String,
startDate: { type: Date, default: Date.now },
endDate: Date,
locked: Boolean,
leagueType: { type: mongoose.Schema.Types.ObjectId, ref: 'LeagueType' },
game: { type: mongoose.Schema.Types.ObjectId, ref: 'Game' },
divisions: [ divisionSchema ],
});
mongoose.model('League', leagueSchema);
var _RemoveDivisionParticipant = function(participantId)
{
return new Promise((resolve,reject) =>{
Models.League.findOne({'divisions.participants._id':participantId})
.populate('divisions')
.populate('divisions.participants')
.exec((err, league) => {
if (err) {return reject(err)}
league.divisions(XXXXX).participants(participantId).remove();
console.log(league.divisions[0].participants[0])
})
})
}
This is what i have so far, but obviously it returns the league object, and i have no way of getting to the participants since I don't know which division the participant is in (Shown by XXXXX in the sample). Any pointers as to what I should do?
You can use $pull to remove an array element based on a condition :
League.update({
'divisions.participants._id': participantId
}, {
$pull: {
'divisions.$.participants': {
"_id": participantId
}
}
}, { multi: true }, function(err, res) {
console.log(res);
});

Resources