New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre, post middleware are not executed on findByIdAndUpdate #964

Closed
skotchio opened this Issue Jun 15, 2012 · 102 comments

Comments

Projects
None yet
@skotchio

skotchio commented Jun 15, 2012

Because a findAndUpdate method is presented to cut down the following code:

 Model.findById(_id, function (err, doc) {
      if (doc) {
          doc.field = 'value';
          doc.save(function (err) {
                 // do something;
          });
      }
 });

to this:

Model
   .findByIdAndUpdate(_id, {$set: {field: 'value'}}, function (err, doc) {
        // do something 
    });

We need to use pre, post middleware exactly the same. At now pre, post middleware are not executed when I make findByIdAndUpdate.

@aheckmann

This comment has been minimized.

Collaborator

aheckmann commented Jun 15, 2012

by design. there are docs involved to call hooks on.

@aheckmann aheckmann closed this Jun 15, 2012

@aheckmann

This comment has been minimized.

Collaborator

aheckmann commented Jun 15, 2012

correction, there are no docs to call hooks on.

@skotchio

This comment has been minimized.

skotchio commented Jun 15, 2012

So? If I want to call pre, post middleware I need to use the first approach?

@aheckmann

This comment has been minimized.

Collaborator

aheckmann commented Jun 15, 2012

yes that is correct. Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove are all commands executed directly in the database.

@oori oori referenced this issue Oct 9, 2012

Merged

Allow "remove" hooks #3

@jessefulton

This comment has been minimized.

jessefulton commented Nov 1, 2012

This should definitely be clarified in the guide, especially if you're talking about validation in that same page and describing findByIdAndUpdate as "better"

@aheckmann

This comment has been minimized.

Collaborator

aheckmann commented Nov 1, 2012

if you click on the link "better" it takes you to the full documentation of
the method where it explains validation etc.

feel free to send a pull request to add something you feel is better. its
pretty easy to do so:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

On Wed, Oct 31, 2012 at 6:03 PM, Jesse Fulton notifications@github.comwrote:

This should definitely be clarified in the guidehttp://mongoosejs.com/docs/documents.html,
especially if you're talking about validation in that same page and
describing findByIdAndUpdate as "better"


Reply to this email directly or view it on GitHubhttps://github.com//issues/964#issuecomment-9967865.

Aaron
@aaronheckmann https://twitter.com/#!/aaronheckmann

@sstadelman

This comment has been minimized.

Contributor

sstadelman commented Oct 25, 2013

Added notes to the middleware doc

Pull request: #1750

@albanm

This comment has been minimized.

albanm commented Jun 10, 2014

Hello,

I know the issue is closed, but I am not entirely satisfied with the answer. Shouldn't there be at least a post-save or post-update middleware for findOneAndUpdate and other similar operations ? Is seems ok since the document is returned. Not feasible for pre middlewares or for Model.update, i agree.

It would greatly improve the capabilities of plugins such as mongoosastic that is currently blind to some operations it should be able to support.

If no middleware, does someone have an idea on how to manage some post update operations in a plugin ?

Thanks

@vkarpov15 vkarpov15 added this to the 4.0 milestone Jun 10, 2014

@jessefulton

This comment has been minimized.

jessefulton commented Jun 20, 2014

@albanm certain methods bypass mongoose completely, so you don't get the middleware hooks. AFAIK, the only way to get the hooks to execute is to use separate find() and save() calls as mentioned above.

@albanm

This comment has been minimized.

albanm commented Jun 20, 2014

I get that and it makes sense. Pre middlewares are out of question as there is no fetching prior to certain methods. But still, mongoose should be able to wrap the update operations that also return the updated documents and trigger some post hooks.

@beetaa

This comment has been minimized.

beetaa commented Jul 2, 2014

I think @albanm is right, People may want the same functionality when they use the same function. How about wrap those 'directly update' methods with some interception of check that if there's any hooks exist? If hook exist, use it, or call original update methods otherwise.

@joafeldmann

This comment has been minimized.

joafeldmann commented Jul 15, 2014

+1

2 similar comments
@sunfuze

This comment has been minimized.

sunfuze commented Jul 25, 2014

+1

@perok

This comment has been minimized.

perok commented Jul 29, 2014

+1

@ludwiksh

This comment has been minimized.

ludwiksh commented Aug 13, 2014

+1

@cebor

This comment has been minimized.

cebor commented Aug 20, 2014

👍

@syzer

This comment has been minimized.

syzer commented Aug 21, 2014

+1

@askhogan

This comment has been minimized.

askhogan commented Aug 29, 2014

👎
I get the feature request, but the longer I work with middleware, I find myself needing to bypass middleware often when calling db update scripts and other items that are not routine to my normal operations. By using static methods it becomes very easy to create a custom wrapper that implements the above feature requests already. Since mongoose is now opening to ideas for version 4, it is unlikely that any API change of this magnitude will happen in v3. I request that this be moved to v4 discussion.

I would however, 👍 if I was able to disable middleware on a save call. I often find myself with a mongoose object that was passed from another function and simply want to perform a .save - in this situation, doing a .save is preferable to writing a new query. If this is possible, please point it out

Either way, amazing library. Kudos to awesome maintainers. Not sure how I would operate without it.

@roymap

This comment has been minimized.

roymap commented Sep 18, 2014

+1

@sailxjx

This comment has been minimized.

sailxjx commented Sep 27, 2014

A simple way to add hooks for these method is overwrite the embed functions:

_update = UserModel.update
UserModel.update = (conditions, update) ->
  update.updatedAt or= new Date
  _update.apply this, arguments

Then every update call of mongoose will fix the updatedAt key of data.

You may give a try of this model limbo. It is a simple wrapper of mongoose model, supporting bind static/method/overwrite to all the schemas, and call rpc methods to query the mongodb.

@nlonguit

This comment has been minimized.

nlonguit commented Apr 6, 2016

if (this._update.$set.password) { this.update({}, { $set: { password: bcrypt.hashSync(this.getUpdate().$set.password)} }); }

This code is working well for me. Thanks @akoskm

@mickyginger

This comment has been minimized.

mickyginger commented Apr 6, 2016

I wonder if it would be possible to add a pre hook for findByIdAndUpdate as well. Would be nice to have both hooks available.

@vkarpov15

This comment has been minimized.

@keegho

This comment has been minimized.

keegho commented Jun 4, 2016

I did it this way and it is working: Just findById then save without updating any fields then use the findByIdAndUpdate method:

dbModel.findById(barId, function (err, bar) {
        if (bar) {

            bar.save(function (err) {
                if (err) throw err;
            });
        }
    });
    dbModel.findByIdAndUpdate(barId, {$set:req.body}, function (err, bar) {
        if (err) throw err;
        res.send('Updated');
    });`
@zilions

This comment has been minimized.

zilions commented Jun 9, 2016

I'm trying to set a property to have the length of an array.

schema.post('findOneAndUpdate', function(result) {
    console.log(result.comments.length);
    this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length });
});

The correct length is logged, although the query never sets totalNumberOfComments, and the field stays at 0 (since the schema references default: 0).

When I console.log(this) at the end of the hook, I can see that my query contains the following:

_update: { '$push': { comments: [Object] }, totalNumberOfComments: 27 }

Although when I turn on debug mode, a query is never logged by Mongoose.

Is there something I'm doing wrong, or is this a bug?

@vkarpov15

This comment has been minimized.

Collaborator

vkarpov15 commented Jun 11, 2016

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec(); need to actually execute the query :) Just be careful, you're gonna get an infinite recursion there because your post save hook will trigger another post save hook

@zilions

This comment has been minimized.

zilions commented Jun 11, 2016

@vkarpov15 Ahhhh right! Then I can just use this.update({} {....}).exec() instead :)
Question though, when using this, it sets the totalNumberOfComments field perfectly, although performs the original update of the findOneAndUpdate too.

For example:

Post.findOneAndUpdate({_id: fj394hri3hfj}, {$push: {comments: myNewComment}})

Will trigger the following hook:

schema.post('findOneAndUpdate', function(result) {
    this.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
}));

Although, the hook will $push to comments the myNewComment again, therefore making a duplicate entry.

@vkarpov15

This comment has been minimized.

Collaborator

vkarpov15 commented Jun 14, 2016

Because you're technically executing the same query -

schema.post('findOneAndUpdate', function(result) {
    this.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
}));

is essentially the same as

var query = Post.findOneAndUpdate({_id: fj394hri3hfj}, {$push: {comments: myNewComment}});
query.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
query.findOneAndUpdate().exec();

If you want to create a new query from scratch, just do

schema.post('findOneAndUpdate', function(result) {
    this.model.update({}, { // <--- `this.model` gives you access to the `Post` model
        totalNumberOfComments: result.comments.length
    }).exec();
}));
@moparlakci

This comment has been minimized.

moparlakci commented Sep 13, 2016

just dont touch your pre save hook,

router.put('/:id', jsonParser, function(req, res, next) {

  currentCollection.findByIdAndUpdate(req.params.id, req.body, function (err, item) {
    if (err) {
        res.status(404);
        return res.json({'error': 'Server Error', 'trace': err});
    }
    item.save(); // <=== this is were you save your data again which triggers the pre hook :)
    res.status(200); 
    return res.json({'message': 'Saved successfully'});
  });
});
@nicky-lenaers

This comment has been minimized.

nicky-lenaers commented Nov 28, 2016

I found that the order matters in which you define a model and define a pre hook. Allow me to demonstrate:

Does not work:

// Create Model
let model = Database.Connection.model(`UserModel`, this._schema, `users`);

// Attach Pre Hook
this._schema.pre(`findOneAndUpdate`, function(next) {
    console.log('pre update');
    return next();
});

Does work:

// Attach Pre Hook
this._schema.pre(`findOneAndUpdate`, function(next) {
    console.log('pre update');
    return next();
});

// Create Model
let model = Database.Connection.model(`UserModel`, this._schema, `users`);

Hope this helps anyone!

@albert-92

This comment has been minimized.

albert-92 commented Nov 30, 2016

I just found out the same thing as @nicky-lenaers.

It works just fine with 'safe'. 'delete'. etc. if you define the hooks after the model is defined.

Is there a workaround do define a 'findOneAndUpdate' hook after the model is defined?

@vkarpov15

This comment has been minimized.

Collaborator

vkarpov15 commented Dec 3, 2016

@albert-92 no not at the moment

@marcusjwhelan

This comment has been minimized.

marcusjwhelan commented Dec 8, 2016

For anyone trying to get something that used to be like

SCHEMA.pre('validate', function(done) {
    // and here use something like 
    this.yourNestedElement 
    // to change a value or maybe create a hashed character    
    done();
});

This should work

SCHEMA.pre('findOneAndUpdate', function(done){
    this._update.yourNestedElement
    done();
});
@johndeyrup

This comment has been minimized.

johndeyrup commented Dec 21, 2016

I can't get post hooks to update the document in the collection.

`module.exports = function (mongoose) {
var mySchema = mongoose.Schema({
id: { type: Number, index: { unique: true } },
field1: { type: String },
field2: { type: String}
}, {
collection: "mySchema",
versionKey: false
});

mySchema.post('findOneAndUpdate', function (result) {
    this.model.update({}, {
        field2: 'New Value'
    }).exec();
});
return mySchema;

}`

mySchema.findOneAndUpdate({id: 1}, {field1: 'test'}, {new: true});

Sets field in collection to { id:1, field1: 'test' ) but should be {id: 1, field1: 'test', field2:'New Value'}
Not sure what I am doing wrong

I can change change the result of the findOneAndUpdate by doing this
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

@marcusjwhelan

This comment has been minimized.

marcusjwhelan commented Dec 21, 2016

I think it might be that you are trying to update the model with an element that already exists on the model. Or possibly that you are selecting it wrong. Try printing out "this" in your mySchema.post. Also you don't seem to have a done() or next() in your post. I'm not very knowledgable on the subject but I know printing out this will at least give you an idea of what you are dealing with.

@johndeyrup

This comment has been minimized.

johndeyrup commented Dec 21, 2016

Isn't the point of update to change an existing document in your model?

this is a query object

You don't need done or next in post hooks as far as I understand.

@marcusjwhelan

This comment has been minimized.

marcusjwhelan commented Dec 21, 2016

Well you have this.model.update which is the schema not the model of the object. I think.. Which means you would have to use

mySchema.post('findOneAndUpdate', function (result) {
    this.model.update({}, {
        $set: { field2: 'New Value'}
    }).exec();
});
return mySchema;

This seems a little backwards to be calling a model function inside of the model. Since you could just use parts of the "this" object that is given to you. you might be better off just using the findOneAndUpdate instead of calling it and then calling another model function on top of it.
in a manager

var data = yourNewData;
self.findOneAndUpdate({id: something._id}, data, {safe: false, new: true})
    .exec()
    .then(resolve)
    .catch(reject);

In my example above I used this._update because that was the update object that needed to be used off of this.

@johndeyrup

This comment has been minimized.

johndeyrup commented Dec 21, 2016

I have tried using $set. It still doesn't change my document in the collection.

@jcyh0120

This comment has been minimized.

jcyh0120 commented Sep 7, 2017

Where can I find the all available pre and post hooks?

@vkarpov15

This comment has been minimized.

Collaborator

vkarpov15 commented Sep 16, 2017

@syzer

This comment has been minimized.

syzer commented Aug 17, 2018

+1

@Automattic Automattic locked as resolved and limited conversation to collaborators Aug 19, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.