Mongoose get and set not working on Array Fields - node.js

I have a collection where I have to store array of userNames which needs to be encrypted(Don't ask why!).
const UserNameSchema = mongoose.Schema({
// Other Fields...
// ...
"userNames": {
type: Array,
set: encryptUserDetails,
get: decryptUserDetails
},
}, {
toObject: { getters: true, setters: true },
toJSON: { getters: true, setters: true },
runSettersOnQuery: true
});
But when I try to write any data into it, it is not calling the set method.
Here is how I am trying to write data in to the DB.
let addUserNameResult = await UserNameModel.update({// find condition omitted},
{
$addToSet: {
"userNames": "new_user_name"
}
}, {
upsert: true,
new: true
});
But this Query still stores the data in plain text without calling my set method.
Any help is appreciated. Thank You.
Edit: encryptUserDetails method added.
let encryptUserDetails = (plainText) => {
// Some encryption algorithm ... (No issues here, It works).
// . . .
return encryptedText;
}

Related

mongoose, omitting/removing a property from each element of array

i have a document like this
{
_id: ...
deletedAt: null
history: [
{
_id: ...
name: ...
deletedAt: null
},
{
_id: ...
name: ...
deletedAt: null
},
]
}
after saving a document i want to return the saved document without properties
deletedAt and history.$.deletedAt
i have post middleware
mySchema.post('save', function () {
this.set('history.$[].deletedAt', undefined);
this.set('history.$.deletedAt', undefined);
this.set('deletedAt', undefined);
});
this middleware removes the deletedAt but it's not removing history.$.deletedAt
when i do history.0.deletedAt this works , but only for the first item of the array. how to make this work for all elements of the array?
also in my model i have specified select: false like this
...
history: {
type: [{
...
deletedAt: { type: Date, default: null, select: false }
...
}],
}
...
but any case history[n].deletedAt is being selected.
You can use Array.map()
// Remove the deletedAt key from each object in the history array
const historyWithoutDeletedAt = savedDoc.history.map(({ deletedAt, ...rest }) => rest);
// Create a new object without the deletedAt key and with the updated history array
const result = { ...savedDoc.toObject(), deletedAt: undefined, history: historyWithoutDeletedAt };
// Return the new object
return result;
solved this way
mySchema.post('save', function () {
this.history.forEach((v, i) => this.set(`history.${i}.deletedAt`, undefined));
this.set('deletedAt', undefined);
});

Mongoose, virtuals and Schemas

I'm developing a NodeJS (Typescript) with Mongoose and when I try to add a virtual to my schema in one of the ways Mongoose's documentation suggests, I get an error saying that the prop virtuals doesn't exist in type SchemaOptions. And a correct error, even though the documentation suggests to use it.
This is what I find in the docs:
// That can be done either by adding it to schema options:
const personSchema = new Schema({
name: {
first: String,
last: String
}
}, {
virtuals: { //--------> I get the error here
fullName: {
get() {
return this.name.first + ' ' + this.name.last;
}
}
}
});
This is what I was trying:
const mySchema = new Schema<MyInterface>(
{
someProp: { type: Types.ObjectId, required: true, ref: "some-collection" },
myList: { type: [listSchema], required: true, default: [] },
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
virtuals: {
getByType: {
get: (type: string) => {
return this.myList.filter((item: Item) => item.type === type);
}
}
}
}
);
In the other hand, I can set my virtual this way:
mySchema.virtual("getByType").get((type: string) => {
return this.myList.filter((item: Item) => item.type === type);
});
I had to do a few workarounds to sort the issue about not resolving the this keyword, but so far I have no problem about it...
The problem is: I use findOne and then try to call my virtual get with my document, but I get a Type Error saying Property 'getByType' does not exist on type 'MyInterface & Document<any, any, MyInterface>'.
Looks like there is a mix of mistake on the documentation and a Typescript problem here.
What do you suggest?
Virtual is intended to be used by a Class. If you want to use it on an instance, use methods instead.
Virtual
mySchema.virtual("getByType").get((type: string) => {
return this.myList.filter((item: Item) => item.type === type);
});
// Usage
MySchema.getByType(type)
Methods
mySchema.methods.getByType = function (type, cb) {
return this.model('MySchema').find({
myList: { $elemMatch: { type } }, // Fill your logic here
}, cb)
};
// Usage
const instance = MySchema.findOne();
const result = instance.getByType(type);

MongoDB : Push new item to the top of the array via findOneandUpdate

I am a nodejs and mongo newbie. I want to push an item into the array which is nested into the document via findOneandUpdate, but would like to add the new item as the first element (top of the array). Here is my structure :
This works fine for adding item to the end of the array:
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail } } }, { upsert: true, new: true })
As I know we can't use unshift with mongodb, but $each and $position can be used for this purpose. So, I have tried this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each:[noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail], $position: 0 } } }, { upsert: true, new: true })
But unfortunately, this gives me an error for the syntax.
I can't figure out how to avoid this and make it work. Or is this the wrong approach and there is any other way to achieve?
Thanks for the help
I have figured out the issue. Following was close enough, except the curly brackets that should be inside the square brackets. So I had to change this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each:[noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail], $position: 0 } } }, { upsert: true, new: true })
To this :
const newNote = await NotesBlock.findOneAndUpdate({ blockId: req.params.blockId }, { $push: { notes: { $each: [{ noteTitle: req.body.noteTitle, noteDetail: req.body.noteDetail }], $position: 0 } } }, { upsert: true, new: true })
And it works as expected now.

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.

Save array values based on ObjectId using mongoose

I try to save each document in an array as ObjectId, like that:
{
materials: {
active: "Steel",
description: "List of materials",
text: "Materials",
value: ["5c44ea8163bfea185e5e2dfb", "5c44ea8163bfea185e5e2dfc"]
}
}
I used an array of promises to save asynchronous each value and save the callback _id:
const reference = {
materials: {
...project.materials,
value: await Promise.all(project.materials.value.map(
async (value) => {
const { _id } = await Material.findOneAndUpdate({ name: value.name }, value, { upsert: true, new: true, setDefaultsOnInsert: true }).exec();
return mongoose.Types.ObjectId(_id);
}
))
},
...
}
There is another more simple way ?

Resources