Accessing object in array of arrays using mongoose - node.js

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

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 do I find object based on child populated property on mongoose

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.

How do I remove an array of referenced Objects when deleting the main document?

This is my MongoDB schema:
const MenuSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
caterer: {
type: Schema.Types.ObjectId,
ref: 'User'
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
}, { timestamps: true })
const ProductSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
price: {
type: String
}
}, { timestamps: true })
What I'm wondering - is how I can delete the array of products, at the same time as I delete the main "Menu" document? When I remove the Menu, I can also assume that the products belonging to the menu should be removed.
At the moment this is how I remove the menu (and tried to remove its products):
await Menu.findOneAndDelete({ _id: req.params.menu_id }, (err, response) => {
if (err) {
console.error(err);
}
Product.remove({ _id: { $in: req.body.products }}, (err, res) => {
if (err) {
console.error(err);
}
console.log('Deleted products');
});
});
However, the products do not get removed. Any suggestions?
Mongoose provides a pre and post middleware on your schema. Which means you can delete all the referenced documents before or after you do an operation on the current schema.
Read more here.
Here's an example, inside your schema add this:
const MenuSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
caterer: {
type: Schema.Types.ObjectId,
ref: 'User'
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
}, { timestamps: true })
const ProductSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
price: {
type: String
}
}, { timestamps: true })
MenuSchema.post('remove', removeProducts);
function removeProducts(doc) {
Products.remove({_id: { $in: doc.products}})
}
Assuming Products is the name of your model.
Try This It works for Me.
await Menu.findOneAndDelete({ _id: req.params.menu_id }, (err, response) => {
if (err) {
console.error(err);
}
Product.remove({ _id: { $in: response.products }}, (err, res) => {
if (err) {
console.error(err);
}
console.log('Deleted products');
});
});
You can use post schema hooks of mongoose as below
schema.post('remove', function(doc) {
console.log('%s has been removed', doc._id);
});
Mongoose Post Hook
But the best approach is to use transactions to execute multiple operations on the database as below.
let session = null;
db.startSession()
.then((_session) =>{
session = _session;
session.startTransaction();
return Menu.deleteOne({ _id: req.params.menu_id });
})
.then(()=> Product.deleteMany({ _id: { $in: req.body.products }}))
.then(()=>{
session.commitTransaction();
})
.catch((err)=>{
session.abortTransaction()
return handleError(err);
})
Mongoose Transactions

Mongoose Virtual field with async getter

I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?
What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}
Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});

Node.js - mongoose. Cannot make a query on an ObjectId

This is my first time asking a question here.
I am using node.js and mongoose and I have the following three schemas:
var CustomerCouponSchema = new Schema({
coupon: { type: Schema.ObjectId, ref: 'Coupon' }
});
var CouponSchema = new Schema({
isActive: { type: Boolean, default: false },
supermarket: { type: Schema.ObjectId, ref: 'Supermarket' }
});
var SupermarketSchema = new Schema({
name: { type: string }
});
When I am trying to perform the following query:
//I have a variable named supermarketId which is an ObjectId
CustomerCoupon.find({ isUsed: false })
.populate({
path: 'coupon',
match: {
supermarket: { $or: [{ $eq: null }, { $eq: supermarketId }] },
isActive: true,
}
}).exec(function(err, data) {
});
I get the following error: Can't use $or with ObjectId.
Any help would be welcome

Resources