Find if object is changed in pre-save hook mongoose - node.js

I am trying to find if the object is changed in pre-save and do some actions accordingly. Followinfg is my code
var eql = require("deep-eql");
OrderSchema.post( 'init', function() {
this._original = this.toObject();
});
OrderSchema.pre('save', function(next) {
var original = this._original;
delete this._original;
if(eql(this, original)){
//do some actions
}
next();
});
It returns false even when I don't change anything!

First of all, you don't need the original object at all. You can access it in the pre hook via this. Secondly post hook executes only after all pre hooks are executed, so your code doesn't make any sense at all (check mongoose docs).
You can do the check by checking isModified in your pre hook and remove the post hook at all.
OrderSchema.pre('save', function(next) {
if(!this.isModified()){
//not modified
}
next();
});
Update
In order to check if some property was modified, pass property name as a parameter to isModified function:
if (this.isModified("some-property")) {
// do something
}

Related

Meteor Client calling findOne in Server Method

I have a client-side form that can create a document upon submission. I want to see if one of the input fields already exists on a Document in the DB though. This would then alert the user and ask them if they want to continue creating the record.
Client-side event
Template.createDoc.events({
'click button[type=submit]'(e, template) {
//This particular example is checking to see if a Doc with its `name` property set to `value` already exists
const value = $('#name');
const fieldName = 'name';
const exists = Meteor.call('checkIfFieldExistsOnDoc', fieldName, value);
if (exists) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
}
});
Server-side Meteor Method
'checkIfFieldExistsOnDoc'(field, val) {
if (this.isServer) {
this.unblock();
check(field, String);
check(val, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You are not authorized.');
}
const findObj = {};
findObj[field] = val;
const fieldsObj = {};
fieldsObj[fieldsObj] = 1;
const doc = Docs.findOne(findObj, {fields: fieldsObj});
return doc;
}
},
My issue is that the client-side code always gets undefined back when calling the Server method. I now understand why, however, I'm not keen on wrapping all of my subsequent client-code into a callback yet.
So - any other ideas on how I can attempt to do this simple feature?
Also - I was thinking of having the client-side page's onCreated do a 1-time server call to get ALL names for all Docs, storing this in memory, and then doing the check upon form submission using this. Obviously, this is inefficient and not-scalable, although it would work
Meteor.call in the client side is always an async call. Then you need implement a callback.
See docs: https://docs.meteor.com/api/methods.html#Meteor-call
Meteor.call('checkIfFieldExistsOnDoc', fieldName, value, function(error, result) {
if (result) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
});
On the client, you can wrap any Meteor.call with a Promise and then use it with async/await. There are some packages on Atmosphere that do this for you to.
I've used this package for years: https://atmospherejs.com/deanius/promise
On the client I often just use await Meteor.callPromise() which returns a response nicely.
Here are a couple of the best write-ups on the many options available to you:
https://blog.meteor.com/using-promises-on-the-client-in-meteor-fb4f1c155f84
https://forums.meteor.com/t/meteor-methods-return-values-via-promise-async/42060
https://dev.to/jankapunkt/async-meteor-method-calls-24f9

Modify data in mongoose pre-validate hook

I would like to modify a document field's data prior to it being validated by mongoose, like so:
mySchema.pre("validate", function (next) {
this.myField = "yay a new value prior to validation" // doesn't work for me
next();
});
Unfortunately, that doesn't work for me. The example above is simplified, in my project I'm trying to prevent an ObjectParameterError from crashing my server, and assigning values in my pre-validate hook doesn't work for me.
Try using .set() method of mongoose Document to access value. Your code should be changed this way
mySchema.pre("validate", function (next) {
this.set("myField", "yay a new value prior to validation")
next();
});

generate hashed password on findOneAndUpdate [duplicate]

I'm trying to update counts on a pre hook. The issue is that for some unknown reason the findOneAndUpdate hook doesn't have access to the document, as far as I can tell.
I would like to do this:
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this.objects = this.objects || [];
this.people = this.people || [];
this.events = this.events || [];
this.objectCount = this.objects.length;
this.peopleCount = this.people.length;
this.eventCount = this.events.length;
next();
});
But for some reason the this in the hook isn't the document, its a Query object which seems about useless.
What am I missing? How do I use a pre hook to update counts on a findOneAndUpdate?
You can do smthng like that ->
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this._update.$set.objects = [];
this._update.$set.people = [];
this._update.$set.events = [];
next();
});
pay attention to _update.$set because in the context "this" will be a query. So you can easily add anything you want!
The documentation states:
Query middleware differs from document middleware in a subtle but important way: in document middleware, this refers to the document being updated. In query middleware, mongoose doesn't necessarily have a reference to the document being updated, so this refers to the query object rather than the document being updated.
An update action generally updates a document that only exists in the database (it tells the MongoDB server: "find document X and set property X to value Z"), so the full document isn't available to Mongoose and, hence, you can't update the counts (which requires access to at least the arrays whose length you want to determine).
As an aside: why do you need separate *Count properties in your schema anyway? If you want to query for arrays matching a certain size, you can use the $size operator on the arrays directly.
If you really do need the count properties, then for each update, you need to track the number of changes you made to each of the arrays (in terms of the number of items added/deleted) and use the $inc operator to adjust the counts.
I had a similar issue when I used the updateOne method and was also going to use the updateOne pre hook to make intermittent update before saving to the database. couldn't find a way for it to work. I ended up using the findOneAndUpdate pre hook and doing the updateOne in it.
schema.pre('findOneAndUpdate', async function(next){
const schema = this;
const { newUpdate } = schema.getUpdate();
const queryConditions = schema._condition
if(newUpdate){
//some mutation magic
await schema.updateOne(queryConditions, {newUpdate:"modified data"});
next()
}
next()
})
Another solution is to use the official MongoDB documentation on middleware. They explain why "this" does not refer to the document itself. You may try something in that sense:
source.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getFilter());
//modify the appropriate objects
docToUpdate.save(function(err) {
if(!err) {
console.log("Document Updated");
}
});
console.log(docToUpdate);
// The document that `findOneAndUpdate()` will modify
next();
});
This worked for me
SCHEMA.pre('findOneAndUpdate', function(next){
this._update.yourNestedElement
next();
});
schema.pre(['findOneAndUpdate'], async function(next) {
try {
const type = this.get('type')
const query = this.getQuery()
const doc = await this.findOne(query)
if (type) {
this.set('type', doc.type)
}
next()
} catch (e) {
next(new BaseError(e))
}
})
mongoose Documentation:
You cannot access the document being updated in pre('updateOne') or pre('findOneAndUpdate') query middleware. If you need to access the
document that will be updated, you need to execute an explicit query
for the document.
schema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery());
console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});

Sequelize afterFind hook not changing data in console.log?

I am trying to run afterFind hook on a model, but the result doesn't seem to change in console.log with JSON.stringify after I run the query. However, the data is changed if I specifically log the attribute.
// on User model
User.hook('afterFind', function(result) {
result.teamCount = 1
return result.save()
}
})
Then when I try to log it:
const result = await User.find()
console.log(result.teamCount) // -> 1
console.log(JSON.stringify(result)) // no "teamCount" attribute present
Not sure what the hook is doing or if save() is even required but something seems off, how can I make the change visible in console.log?
You should declare the teamCount field as virtual:
sequelize.define('user', {
...
teamCount: {
type: DataTypes.VIRTUAL
}
...
});

Mongoose - how to tap schema middleware into the 'init' event?

It is suggested in the Mongoose docs that I should be able to control the flow using middleware that plugs in to the "init" hook.
However, I have so far had success only with "save" and "validate".
When I do something like this, neither of these middleware ever get called:
MySchema.post( "init", function (next) { console.log("post init") });
MySchema.pre( "init", function (next) { console.log("pre init") });
Am I missing something?
It turns out that the "init" event/hook is not fired when creating a new Model, it is only fired, when loading an existing model from the database. It seems that I should use the pre/validate hook instead.
I have successfully used middleware like MySchema.post('init', function() { ... }); with Mongoose which is then executed for each model instance loaded in a find query. Note that there isn't a next parameter to call with this middleware, it should just return when done.

Resources