Skip to content
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
Closed

pre, post middleware are not executed on findByIdAndUpdate #964

skotchio opened this issue Jun 15, 2012 · 102 comments

Comments

@skotchio
Copy link

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
Copy link
Collaborator

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

@aheckmann
Copy link
Collaborator

correction, there are no docs to call hooks on.

@skotchio
Copy link
Author

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

@aheckmann
Copy link
Collaborator

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

@jessefulton
Copy link

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
Copy link
Collaborator

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
Copy link
Contributor

Added notes to the middleware doc

Pull request: #1750

@albanm
Copy link

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
Copy link

@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
Copy link

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
Copy link

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
Copy link

+1

2 similar comments
@sunfuze
Copy link

sunfuze commented Jul 25, 2014

+1

@perok
Copy link

perok commented Jul 29, 2014

+1

@ludwiksh
Copy link

+1

@cebor
Copy link

cebor commented Aug 20, 2014

👍

@syzer
Copy link

syzer commented Aug 21, 2014

+1

@askhogan
Copy link

👎
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
Copy link

roymap commented Sep 18, 2014

+1

@sailxjx
Copy link

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.

@akoskm
Copy link

akoskm commented Apr 6, 2016

@nlonguit I guess it will. But you can access through this the fields that are going to be updated and you could do something like:

if (this._fields.password) { // <- I'm sure about this one, check in debugger the properties of this 
    this.update({}, { $set : { password: bcrypt.hashSync(this.getUpdate().$set.password) } });
}

@nlonguit
Copy link

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
Copy link

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
Copy link
Collaborator

@mickyginger we have hooks for findOneAndUpdate, and findByIdAndUpdate() just calls findOneAndUpdate() under the hood

@keegho
Copy link

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
Copy link

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
Copy link
Collaborator

@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
Copy link

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
Copy link
Collaborator

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Collaborator

@albert-92 no not at the moment

@marcusjwhelan
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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

@jcyh0120
Copy link

jcyh0120 commented Sep 7, 2017

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

@vkarpov15
Copy link
Collaborator

Middleware docs http://mongoosejs.com/docs/middleware.html

@syzer
Copy link

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.
Labels
None yet
Projects
None yet
Development

No branches or pull requests