mongoose: mongoose 4.0.1: pre "update" middleware this object does not return model object

Hi, I’ve a problem with the newest version of Mongoose. I’m creating an API with Express and Mongoose 4.0.1, and I’m not sure if I’m doing anything wrong, but the fact is whenever I try to use the new pre update middleware the same way I use pre save middleware, this object does not return the object being updated, instead it returns the Query object.

Example of what I’m trying to explain:

ExerciseSchema.pre('save', function (next, done) {
        var self = this;
        console.log('exercise:', self); // returns exercise object
        // some validations here
       next();
});
ExerciseSchema.pre('update', function (next, done) {
        var self = this;
        console.log('exercise:', self); // returns Query object instead of exercise object
        // some validations here
       next();
});

This is what I get in this reference object within the middleware, and I don’t know how it’s useful to me.

{ _mongooseOptions: {},
  mongooseCollection: 
   { collection: { s: [Object] },
     opts: { bufferCommands: true, capped: false },
     name: 'exercises',
     conn: 
     ... },
   ...
}

Looking at the source code, some of its properties are defined inside Query function defined in ./node_modules/mongoose/lib/query.js:

Is this something unexpected or I am doing anything wrong? It would be interesting to have a solution because I don’t like the idea of validating within a middleware on object saving and being forced to run validations directly on controller when updating.

The same thing happens with findOneAndUpdate pre middleware, but I don’t expect it to return the exercise before finding it. In fact and in my honest opinion, I think it would be interesting that findAndUpdate and findOneAndUpdate pre middlewares triggered find(One) and update middlewares, in this order, instead of having its own middleware.

Thanks in advance for your help.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 1
  • Comments: 40 (3 by maintainers)

Commits related to this issue

Most upvoted comments

Ah ok I see you’re using findOneAndUpdate() rather than update(). These are two distinct functions with distinct hooks, so if you want to add update ops to findOneAndUpdate in a pre hook, you should do

schema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { $set: { key: 'value' } });
});

Sorry I wrote that in a moment of stress 😃

Just the brittleness of version compatibility really. It’s not an obvious issue, and leaves a lot of people scratching their heads.

schema.pre('update', function() {
  var v = this.getUpdate().valueToSanitize;
  this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});

So if you use the native mongodb driver directly and do coll.findOneAndUpdate({ a: 1 }, { __v: 3 }), mongodb will take the first document with a = 1 and replace it with the document { __v: 3 } modulo _id. In other words, it will overwrite the existing document. In order to just set the key ‘__v’, you need to do coll.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } }).

This behavior has always been controversial and error-prone, so by default mongoose prevents you from overwriting the document. In other words, MyModel.findOneAndUpdate({ a: 1 }, { __v: 3 }) becomes MyModel.collection.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } }) unless you set the overwrite: true option. However, you can do strange things like MyModel.update({}, { $set: { b: 2 } }).findOneAndUpdate({ a: 1 }, { __v: 3 }, { overwrite: true }) that make it quite confusing as to what the current state of getUpdate() is, so we just leave the status of update as-is for middleware.

I’m trying to grok all of this but am having a hard time determining if I can modify the doc that will be returned from a find using these middleware methods. Specifically, I’d like to “decorate” the response by adding additional fields.

For instance, if I have a Schema:

const Thing = new mongoose.Schema({
  name: {type: String}
});

Thing.post('find', function(doc, next){
  doc.newfield = "example text";
  next();
}

Is this possible?

The reason newfield isn’t defined in the schema is that depending on the values of Thing, different field names are needed.

Thanks!

this.update({ field: val }); will add { $set: { field: val } } to the update operation before it happens.

By design - the document being updated might not even be in the server’s memory. In order to do that, mongoose would have to do a findOne() to load the document before doing the update(), which is not acceptable.

The design is to enable you to manipulate the query object by adding or removing filters, update params, options, etc. For instance, automatically calling .populate() with find() and findOne(), setting the multi: true option by default on certain models, access control, and other possibilities.

findOneAndUpdate() is a bit of a misnomer, it uses the underlying mongodb findAndModify command, it’s not the same as findOne() + update(). As a separate operation, it should have its own middleware.

@vkarpov15

Looking at the example you gave above …:

schema.pre('update', function() {
  var v = this.getUpdate().valueToSanitize;
  this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});

This does not work for me. The this.getUpdate().value does not contain my property value as the example suggests. Instead it’s available under this.getUpdate().$set.property.

Is it supposed to be like that? Am I understanding something wrong? Did the API change? Is there any documentation about this?

@PDS42 you’ll have to run a separate query to load the document from mongodb

How is one supposed to check for document fields before updates ? Say I need to check whether certain fields are filled to set document as “valid”, how can I access the whole document, and not just fields being updated ? this is referring to the query here

pre method should not work with this.update, because it is “pre”-save and there is nothing to update yet, while this.newProp = "value" works perfectly, thanks guys.

My case is:

UserSchema.pre('save', function(next) {
    this.created = Date.now()
    next()
})

Exactly the same for me as mentioned by @qqilihq

image

So desired behavior could be achieved with this code:

userSchema.pre('update', function (next) {
  const newEmail = this.getUpdate().$set.email
  if (newEmail) {
    this.update({}, {$set: {email: normalizeEmail(newEmail)}})
  }
  next()
})

But it looks like I’m trying to use some fields that are not intended for this.