My schema is like the following:
const Author = new Schema({
name: String,
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: "Post" }]
});
const Post = new Schema({
text: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: "Author" }
});
If I use deleteMany() to delete a bunch of posts, what is the best way to remove the ObjectId entries from the Author's object?
I tried to use the deleteMany pre hook, however it only passes how many doc are deleted and not the actual ObjectIds of the posts.
You could create the query object that is used for the deleteMany, and instead use it to query for the documents via find. That will give you all of the targeted documents, which you can then delete via deleteMany.
Afterwards, if the deleted count matches the length of fetched documents, then you can safely remove all from the author. If the total deleted does not match the length of fetched documents, then you can iterate through all initially-fetched documents, attempt to fetch it, and if it's not found, then that means it's deleted and you can safely remove from author.
Example in typescript:
const queryObj = { foo: 'bar' };
const postsCollection = mongoDb.getCollection<{ _id: ObjectId }>('posts');
const postsToDelete = await postsCollection.find(queryObj).toArray();
const deleteManyResult = await postsCollection.deleteMany(queryObj);
if (deleteManyResult.deletedCount === postsToDelete.length) {
for (const deletedPost of postsToDelete) {
// delete from author
}
} else {
for (const potentiallyDeletedPost of postsToDelete) {
const post = await postsCollection.findOne({ _id: potentiallyDeletedPost._id });
if (!post) {
// delete from author
} else {
// handle non-deleted post
}
}
}
Related
I am trying to create a database for my Giveaway Bot. It consist of 2 collections, Main (holding settings) and Giveaway which is nested in under the Main collection. I can create my giveaway's without problems. However I want to add some data later on using findOneAndUpdate.
Running the code below I always get this error: Updating the path 'giveaways.duration' would create a conflict at 'giveaways'. Can anyone help solving this issue ?
schema.js
const giveawaySchema = new mongoose.Schema({
_id: String,
destination: String,
duration: String,
winners: String,
price: String,
})
const mainSchema = new mongoose.Schema({
_id: String,
log_channel_id: String,
admin_roles: [],
giveaways: [giveawaySchema],
const Main = mongoose.model("mainSchema", mainSchema);
const Giveaway = mongoose.model("giveawaySchema", giveawaySchema);
module.exports = { Main, Giveaway }
});
Part of my code used for updating:
const mongoose = require("mongoose")
const {Main, Giveaway} = require("../models/schema.js")
const newestGiveaway = await Main.findOneAndUpdate(
{
_id: guildId,
'giveaways._id': giveaway_id,
},
{
"$set":{
"giveaways.duration": "3d",
"giveaways.winners": "20",
"giveaways.price": "Price to Win",
},
},
{
upsert: true,
}
Thank you for your help :)
A small side question. I have fetched the Main document (the parent) before already can I make my search cheaper/ more efficent by only searching through this instead of running the findOneandUpdate method on the whole database ?
Edit 1:
I found that it is neccesary to use the $ operator and have updated my code. However I still get the same error:
{
$set:{
"giveaways.$.duration": "3d",
"giveaways.$.winners": "20,
"giveaways.$.price": "Price to Win",
},
},
Edit 2:
Just to clarify, the creation and nesting of the giveawaySchemas works but I am not able to update the nested document by using the code above.
My child component is already created by using the code below. I now want to update this child (newGiveaway with _id of 1)
const currentGuild = await Main.findOne({_id: guildId})
const newGiveaway = await Giveaway.create({
_id: 1,
destination: 12345678,
});
currentGuild.giveaways.push(newGiveaway)
You can change your schema declaration to use a ref to the giveawaySchema:
const giveawaySchema = new mongoose.Schema({
_id: String,
destination: String,
duration: String,
winners: String,
price: String,
})
const mainSchema = new mongoose.Schema({
_id: String,
log_channel_id: String,
admin_roles: [],
giveaways: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'giveawaySchema'
}],
const Main = mongoose.model("mainSchema", mainSchema);
const Giveaway = mongoose.model("giveawaySchema", giveawaySchema);
module.exports = { Main, Giveaway }
Then, you will just need to update your giveaways directy:
const mongoose = require('mongoose');
const { Giveaway } = require('../models/schema.js');
const newestGiveaway = await Main.findByIdAndUpdate(
giveaway_id,
{
duration: '3d',
winners: '20',
price: 'Price to Win',
},
{
new: true,
}
);
In the mainSchema you define giveaways field as an array of giveawaySchema object. So, you have to treat it as an array, not an object. If you want to treat it as an object, you will have to update mainSchema by removing square bracket at giveawaysSchema.
Relevant Question for how to pushing item into mongo array
When i fetch new alerts, i want to check if the ID of the new alert was already recorded. The issue is that that ID is nested inside an array. There's the alertsDetails array, which contains objects and those objects have an _ID filed which is what i want to check. I am not sure how to achieve that. I got the code below but then i have to iterate over the result to check the exists value. Im sure there must be a better way.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const G2AlertsSchema = new Schema(
{
status: { type: String, required: true },
openDate: { type: Date, required: true },
alertType: { type: Array, required: true },
severity: { type: Array, required: true },
locationName: { type: Array, required: true },
history: { type: Array, required: true },
alertDetails: { type: Array, required: false },
assignedTo: { type: Schema.Types.ObjectId, ref: 'user' },
},
{
timestamps: true,
},
);
const G2Alerts = mongoose.model('G2Alert', G2AlertsSchema);
module.exports = G2Alerts;
This is the code i found on mongodb's website. I just want to see if the ID exists only. Basically when i fetch the new alerts i get an array and i iterate over it, i want to check each item's ID against what's inside the Database. If it's there, skip and go to the next. If it's new, then create a new alert and save it.
const exists = await G2Alerts.aggregate([
{
$project: {
exists: {
$in: ['5f0b4f508bda3805754ab343', '$alertDetails._id'],
},
},
},
]);
EDIT: Another thing. I am getting a eslint warning saying i should use array iteration instead of a for loop. The issue is, i need to use await when looking up the Alert ID. If i use, reduce or filter, i can't use await. If i use async inside the reduce or filter function, then it will return promises in or just an empty array.
This below works, based on the answer provided by Tom Slabbaert
const newAlertsData = [];
for (let item of alertData.data.items) {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
newAlertsData.push(item);
}
}
if (newAlertsData.length !== 0) {......
But this does not
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
filtered.push(item);
}
return filtered;
}, []);
You're not far off, here is an example using the correct syntax:
const exists = await G2Alerts.findOne({"alertDetails._id": '5f0b4f508bda3805754ab343'}});
if (!exists) {
... do something
}
This can also be achieve using aggregate with a $match stage instead of a $project stage or even better countDocuments which just returns the count instead of the entire object if you do not require it.
One more thing I'd like to add is that make sure alertDetails._id is string type as you're using string in you're $in. otherwise you'll need to cast them to ObjectId type in mongoose like so:
new mongoose.Types.ObjectId('5f0b4f508bda3805754ab343')
And for Mongo:
import {ObjectId} from "mongodb"
...
new ObjectId('5f0b4f508bda3805754ab343')
EDIT
Try something like this?
let ids = alertData.data.items.map(item => item._id.toString());
let existing = await G2Alerts.distinct("alertsDetails._id", {"alertsDetails._id": {$in: ids}});
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString())) {
return [item].concat(filtered)
}
return filtered;
}, []);
This way you only need to call the db once and not multiple times.
Final code based on the provided answer.
const ids = alertData.data.items.map(item => item._id);
const existing = await G2Alerts.find({ 'alertDetails._id': { $in: ids } }).distinct(
'alertDetails._id',
(err, alerts) => {
if (err) {
res.send(err);
}
return alerts;
},
);
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString()) && item.openDate > dateLimit) {
return [item].concat(filtered);
}
return filtered;
}, []);
This is my ObjectIds array -
obj_ids = [
"5ee71cc94be8d0180c1b63db",
"5ee71c884be8d0180c1b63d9",
"5ee71c494be8d0180c1b63d6",
"5ee71bfd4be8d0180c1b63d4"
]
I am using these objectids to serach whether they exist in the db or not and based on that I want to send the response to server.
This is the code I am trying but I dont know how to populate the array and send it to the server.
var msg = [];
obj_ids.map((ele) => {
Lead.find({ _id: ele._id }, async function (error, docs) {
if (docs.length) {
msg.push(
`Lead already exist for Lead id - ${ele._id} assgined to ${docs[0].salesPerson}`
);
} else {
msg.push(`Lead doesn't exist for Lead id: ${ele._id}`);
const newDuty = new AssignedDuty({
duty: ele._id,
salesPerson: req.body.salesPerson,
});
await newDuty.save();
}
});
});
res.json(msg);
By doing this approach I am getting an empty array. I cannot put res.json(msg) inside the loop. If it is possible by using async-await, please guide me through.
You don't need to make multiple queries to find whether given object ids exist in the database.
Using $in operator, you can make one query that will return all the documents where the _id is equal to one of the object id in the list.
const docs = await Lead.find({
_id: {
$in: [
"5ee71cc94be8d0180c1b63db",
"5ee71c884be8d0180c1b63d9",
"5ee71c494be8d0180c1b63d6",
"5ee71bfd4be8d0180c1b63d4"
]
}
});
After this query, you can check which object id is present in the docs array and which is absent.
For details on $in operator, see $in comparison operator
Your code can be simplified as shown below:
const obj_ids = [
"5ee71cc94be8d0180c1b63db",
"5ee71c884be8d0180c1b63d9",
"5ee71c494be8d0180c1b63d6",
"5ee71bfd4be8d0180c1b63d4"
];
const docs = await Lead.find({
_id: { $in: obj_ids }
});
const msg = [];
obj_ids.forEach(async (id) => {
const doc = docs.find(d => d._id == id);
if (doc) {
msg.push(
`Lead already exist for Lead id - ${doc._id} assgined to ${doc.salesPerson}`
);
}
else {
msg.push(`Lead doesn't exist for Lead id: ${id}`);
const newDuty = new AssignedDuty({
duty: id,
salesPerson: req.body.salesPerson
});
await newDuty.save();
}
});
res.json(msg);
I have a mongoDB database containing Events. Each event is defined by a number of fields, including an array of genres (which are subdocuments consisting of a {genre and subGenre}).
For example: an event could be of {genre: "music", subGenre: "jazz"}, and {genre: "music", subGenre: "blues"}. See event.js - the model. In this case, 2 "genre" sub documents are added to the "genres" array in the Event document in the database.
Back in my node application - see query.ts - I am trying to work out how to run a query that lets the user search for all events that match their genre preferences.
So:
the Event is defined by an array of genres (in the database), and
the user's preferences are defined by an array of genres (in the
application).
I am looking to define a mongoDB query that returns all Events where there is a match of any 1 {genre, subGenre} combination between the 2 arrays.
I looked into $in Query Selector in the mongoDB documentation and suspect it might need to be used... but programmatically, how do I write a query that expands to include all the values in the variable "searchGenres" in query.ts?
Thanks a lot in advance for your thoughts.
event.js: mongoDB Model for 'Events" defined using Mongoose - snippet:
let mongoose = require('mongoose');
let EventSchema = new mongoose.Schema({
genres: [
{
genre: String,
subGenre: String
}
]
)};
module.exports = mongoose.model('Event', EventSchema);
query.ts:
import mongoose = require('mongoose');
let Event = require ('../models/event');
class Genre {
genre: string;
subGenre: string;
constructor (gen: string, sub: string) {
this.genre = gen;
this.subGenre = sub;
}
}
async function runQuery()
{
let searchGenres : Array<Genre> = new Array<Genre>();
// populate searchGenres with some data here... e.g.
const searchGenre1 : Genre = new Genre ('music', 'jazz');
const searchGenre2 : Genre = new Genre ('music', 'rock');
searchGenres.push(searchGenre1);
searchGenres.push(searchGenre2);
// Query logic here:
// Return all events from the database, if the 'genres' array
// in the document matches any element in the searchGenres array
// defined above..
const events = await Event.find ({
'genres': {?help? }
});
}
```
After some self-education today, I have come up with a solution, which I am happy with - to append to end of query.ts:
type GenreQuerySelector = { genres: { $elemMatch: { 'genre': string; 'subGenre': string; }; }; };
let querySelectors : Array< GenreQuerySelector > = new Array< GenreQuerySelector >();
for (let genre of searchGenres) {
const genreQuery : GenreQuerySelector = {
genres: {
$elemMatch: {
'genre': genre.genre,
'subGenre': genre.subGenre
}
}
};
querySelectors.push(genreQuery);
};
const events = await Event.find ({ $or: querySelectors }).exec();
I want to access the content of array of document in my model, but I can't and return undefined.
here is my model(Project.js):
var mongoose = require('moongoose');
var Schema = mongoose.Schema;
var User = require("./Users");
var ProjectSchema = new Schema({
name: String,
description: String,
owner: {
type: mongoose.SchemaTypes.ObjectId,
ref: "User"
},
contributor: [{
type: mongoose.SchemaTypes.ObjectId,
ref: "User"
}]
});
module.exports = mongoose.model('Project', ProjectSchema);
and my Api:
var Project = require('./Project')
await Project.find({owner: userId, name: name})
.then(project => {
console.log(project);
console.log(project.contributor);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
when i try console.log(project); return expected output but in console.log(project.contributor); return undefined
I've also searched the web but couldn't find anything right and clear solution
I appreciate any help :)
As you are expecting to find only one project, change find by findOne method. Other case you are searching for several projects and you are going to receive an array instead of an object.
Your output from Project.find() (See) will be an array of objects from the database.
If you will only have 1 object as a result then you can use project[0].contributor because project is an array with 1 object inside it, which is on the index 0.
If the result might have many objects in the array then you should iterate through the result to get each of the data individually.
project.forEach(p => console.log(p.contributor))