remove document after virtual populating - node.js

I am aggregating a collection. Then virtually populating that document but some of the virtual documents have banned: true so i want to remove that documents.
This is the code:
const populatedManager = await Report.populate(reportedManagers, {
path: "managerId",
options: { select: { name: 1, avatar: 1 }, lean: true },
match: { banned: false },
});
Now i have to remove that all those documents which have managerId: null because match: { banned: false }.
I have tried to make populatedManager a POJO by chaining this method to populate lean() or this execPopulate().then((populatedManager) => populatedManager.toObject()); but it gives me this error message: "Report.populate(...).execPopulate is not a function".
Another try could be this:
populatedManager.forEach((doc, i) => {
if (doc.managerId === null) delete populatedManager[i];
});
const final = await Report.populate(populatedManager, {
path: "reportTypes.reasonId",
options: {
select: { title: 1 },
},
});
But this gives me error in the below virtual population message: "Cannot read properties of undefined (reading '_doc')"
Please help me to remove the document.

Related

Update document inside array of objects in mongoose

I need to update objects inside an array so I'm trying but I get the following error:
error Plan executor error during findAndModify :: caused by :: The
positional operator did not find the match needed from the query.
This is my code:
const payment = await Purchase.findByIdAndUpdate(
{ '_id': req.body.id, 'payments._id': req.body.paymentId },
{
$set: {
'payments.$.status': false
}
}
,{ new: true });
payments object on Model:
payments: [
{
createdBy: [Object],
createdAt: '08/13/22',
paymentNumber: 0,
previousBalance: 3747.68,
paymentAmount: 3747.68,
outstandingBalance: 0,
status: true,
_id: new ObjectId("62f83f3c22e4f67dde8cb85a"),
lastModificationBy: [],
disabledBy: []
}
]
while using fineByIdAndUpdate you only need to pass id of document to be updated.
const payment = await Purchase.findByIdAndUpdate(req.body.paymentId,{status:false},{new:true} )
for using findByIdAndUpdate, you need to add this runValidators
const payment = await Purchase.findByIdAndUpdate(req.params.id,
'payments.$.status': false
}, {runValidators: true}
while same update can be done by this as well
const payment = await Purchase.findByIdAndUpdate(
req.body.paymentId,
{
status:false
}, {
new:true
}
)

Can't remove last item in mongoose array of type Schema.Types.ObjectId with ref

In my mongoDb database if I have 2 or more items in the "folders" array with ObjectIds, I can remove all of them (except the last one) with the same function (below). But when there is only one ObjectId left in the array, mongoose does not remove it. The response form "updatedFolder" actually logs "folders: []" but the last item is still there. I'm not sure if it has something to do with my schema.
Schema
name: { type: String, required: true },
author: {},
parentFolder: {
type: Schema.Types.ObjectId,
ref: "Folder",
},
blalis: [
{
type: Schema.Types.ObjectId,
ref: "Blali",
},
],
folders: [
{
type: Schema.Types.ObjectId,
ref: "Folder",
default: [],
},
],
});
Updating in express with router.patch
router.patch("/:id", getFolder, async (req, res) => {
//Here req.body.folders is [] (an empty array)
if (req.body.folders !== undefined) {
res.folder.folders = req.body.folders;
}
try {
const updatedFolder = await res.folder.save();
res.json({
updatedFolder,
message: "Folder updated.",
});
} catch (err) {
res.status(400).json({ message: err.message });
}
});
My getFolder function uses .populate, but I don't think that's the problem
async function getFolder(req, res, next) {
let folder = null;
try {
folder = await Folder.findById(req.params.id).populate([
{
path: "folders",
},
{ path: "blalis" },
]);
if (folder === null) {
return res.status(404).json({ message: "Folder not found" });
}
} catch (err) {
return res.status(500).json({ message: err.message });
}
res.folder = folder;
next();
}
In the end on mongo Atlas, I still see the ObjectId, even though in the .patch it was set to an empty array: []
folders:Array
0: ObjectId("6237e6e8c4b9a82c10b74e1d")
Ok this one was hard to track.
Before I removed the ObjectId, from the "folders" array. I deleted the ObjectId instance, so for some reason I was not able to remove the last item from the array if the instance was already deleted.
SOLUTION: First remove the ObjectId from the array and then delete the actual instance

Mongoose unique if not null and if state

I have a unique index like this
code: {
type: String,
index: {
unique: true,
partialFilterExpression: {
code: { $type: 'string' }
}
},
default: null
},
state: { type: Number, default: 0 },
but When the state is 2 (archived) I want to keep the code, but it should be able to reuse the code, so it cannot be unique if state is 2.
Is there any away that I could accomplish this?
This is possible, though it's through a work around documented here https://jira.mongodb.org/browse/SERVER-25023.
In MongoDB 4.7 you will be able to apply different index options to the same field but for now you can add a non-existent field to separate the two indexes.
Here's an example using the work around.
(async () => {
const ItemSchema = mongoose.Schema({
code: {
type: String,
default: null
},
state: {
type: Number,
default: 0,
},
});
// Define a unique index for active items
ItemSchema.index({code: 1}, {
name: 'code_1_unique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 0}}
]
},
unique: true
})
// Defined a non-unique index for non-active items
ItemSchema.index({code: 1, nonExistantField: 1}, {
name: 'code_1_nonunique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 2}}
]
},
})
const Item = mongoose.model('Item', ItemSchema)
await mongoose.connect('mongodb://localhost:27017/so-unique-compound-indexes')
// Drop the collection for test to run correctly
await Item.deleteMany({})
// Successfully create an item
console.log('\nCreating a unique item')
const itemA = await Item.create({code: 'abc'});
// Throws error when trying to create with the same code
await Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code')})
// Change the active code
console.log('\nChanging item state to 2')
itemA.state = 2;
await itemA.save();
// Successfully created a new doc with sama code
await Item.create({code: 'abc'})
.then(() => console.log('\nSuccessfully created a new doc with sama code'))
.catch(() => console.log('\nThrowing a duplicate error'));
// Throws error when trying to create with the same code
Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code again')})
})();
This is not possible with using indexes. Even if you use a compound index for code and state there will still be a case where
new document
{
code: 'abc',
state: 0
}
archived document
{
code: 'abc',
state: 2
}
Now although you have the same code you will not be able to archive the new document or unarchive the archived document.
You can do something like this
const checkCode = await this.Model.findOne({code:'abc', active:0})
if(checkCode){
throw new Error('Code has to be unique')
}
else{
.....do something
}

Sequelize: Virtual column is not returned in query results

I can't get this very simple virtual column to work (surnameName). It is not returned in query results, but it does not throw any error either.
My model (I removed irrelevant fields):
const Person = connectionPool.define('person', {
ID: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
name: Sequelize.STRING,
surname: Sequelize.STRING,
surnameName: {
type: Sequelize.VIRTUAL(Sequelize.STRING, ['surname', 'name']),
get() {
return this.getDataValue('surname') + ' ' + this.getDataValue('name');
}
}
});
This is how I query the model:
const cfg = {
where: {},
limit: 10,
raw: false, // tried with and without this line
attributes: ['surnameName']
}
models.Person.findAll(cfg)
.then(results => {
console.log(results[0]);
})
And this is what I get in the console log:
person {
dataValues: { surname: 'Baggins', name: 'Frodo' }, // all other fields are autoexcluded by Sequelize
...
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true, // is true even if I set 'raw' to false in findAll options
attributes: [ 'surnameName', 'surname', 'name' ] // <= surnameName is there!
}
}
Virtual column is not returned in the results, however the logged instance shows that the internal _options.attributes array does contain the field, so Sequelize somehow acknowledges that it should be added. I tried explicitly turning raw=false, as I read that raw excludes virtual columns, but it has no effect. The results are definitely not raw.
What can be wrong here? Any help will be appreciated!
It is possible to hide properties javascript object. Here is an example
function Person(fName, lName) {
this.fName = fName;
this.lName = lName;
Object.defineProperties(this, {
fullName: {
get : function () {
return this.fName + " " + this.lName;
}
}
});
}
const ratul = new Person("Ratul", "sharker");
console.log(ratul);
console.log(ratul.fullName);
Look closely that console.log(ratul) does not print fullName, but fullName is sitting here, returning it's value seen in console.log(ratul.fullName).
Similar thing can be found in this answer.

Looping through objects and adding them to Sequelize db

I have an array of objects that I like to put into SQL via Sequelize and I'm running into issues.
[
{ owner: false,
id: '2342365',
name: 'awrAPI' },
{ owner: false,
id: '5675689699',
name: 'TgatAPI' },
{ owner: true,
id: '57456767',
name: 'ytasyAPI' }
[
Currently the way i have it set up is through a simple for in.
for( var key in guilds ) {
Guild
.findOrCreate({where: {primid: guilds[key].id}, defaults: {
primid: guilds[key].id,
name: guilds[key].name,
owner: guilds[key].icon
}})
.spread(function(guild, created) {
console.log(guild.get({
plain: true
}))
console.log(created)
})
}
I assume this is wrong and was wondering if there is a better way to loop through my object and chain the findorcreates. Currently it goes through the first object, but then does not add any more. I've been looking into using Bluebird, but I'm not too familiar with using promises. Thoughts?
var myData = [
{
owner: false,
id: '2342365',
name: 'awrAPI'
},
{
owner: false,
id: '5675689699',
name: 'TgatAPI'
},
{
owner: true,
id: '57456767',
name: 'ytasyAPI'
}
];
Promise.all(myData.map(function(value) {
// Do your thing
return Guild.findOrCreate...
})).then(function() {
// All is resolved do your next thing
})
An alternative would be to utilize squelize's bulkCreate model method for multiple records insertion.
link to sequelize's bulkCreate docs
quick snippet here:
const dataForInsertion = [
{
username:"daniel",
currentORM:"Sequelize"
},
{
username:"lekan",
currentORM:"Mongoose"
},
]
Guild.bulkCreate(dataForInsertion)
.then(()=>{})
.catch(()=>{})

Resources