Mongoose .pre('save') does not trigger
Asked Answered
M

6

23

I have the following model for mongoose.model('quotes'):

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var quotesSchema = new Schema({
    created: { type: String, default: moment().format() },
    type: { type: Number, default: 0 },
    number: { type: Number, required: true },

    title: { type: String, required: true, trim: true},
    background: { type: String, required: true },

    points: { type: Number, default: 1 },
    status: { type: Number, default: 0 },
    owner: { type: String, default: "anon" }
});

var settingsSchema = new Schema({
    nextQuoteNumber: { type: Number, default: 1 }
});

// Save Setting Model earlier to use it below
mongoose.model('settings', settingsSchema);
var Setting = mongoose.model('settings');

quotesSchema.pre('save', true, function(next) {
  Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
    if (err) { console.log(err) };
    this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
    next();
  });
});

mongoose.model('quotes', quotesSchema);

There is an additional Schema for mongoose.model('settings') to store an incrementing number for the incrementing unique index Quote.number im trying to establish. Before each save, quotesSchema.pre('save') is called to read, increase and pass the nextQuoteNumber as this.number to the respectively next() function.

However, this entire .pre('save') function does not seem to trigger when saving a Quote elsewhere. Mongoose aborts the save since number is required but not defined and no console.log() i write into the function ever outputs anything.

Meletius answered 9/5, 2015 at 15:4 Comment(0)
L
64

Use pre('validate') instead of pre('save') to set the value for the required field. Mongoose validates documents before saving, therefore your save middleware won't be called if there are validation errors. Switching the middleware from save to validate will make your function set the number field before it is validated.

quotesSchema.pre('validate', true, function(next) {
  Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
    if (err) { console.log(err) };
    this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
    next();
  });
});
Lew answered 9/5, 2015 at 16:0 Comment(2)
Thanks. I ended up just doing the action in my pre('save') whenever actually saving, since it ended up not happening all that often. I take it that if i wouldn't have set required: true on my number, my variant would have worked too?Meletius
Thanks so much! I had a required unique field which was not initialized and trying the pre save hook didn't work until I switched to validate like you said.Usury
M
8

For people who are redirected here by Google, make sure you are calling mongoose.model() AFTER methods and hooks declaration.

Mozellamozelle answered 25/1, 2021 at 11:31 Comment(0)
F
2

In some cases we can use

UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, function (next) {

But i'm using "this", inside the function to get data, and not works with findOneAndUpdate trigger

I needed to use

  async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
    const result = await this.userModel.findById(id)
    Object.assign(result, doc)
    await result?.save()
    return result
  }

Instead of

  async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
    const result = await this.userModel.findByIdAndUpdate(id, doc, { new: true, useFindAndModify: false })
    return result
  }
Friesen answered 15/1, 2021 at 1:41 Comment(1)
Can use in-built mongoose method result.set(doc) instead of Object.assign(...)Introversion
A
2

The short solution is use findOne and save

const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();
Aerobiology answered 17/9, 2022 at 11:37 Comment(0)
R
0

I ran into a situation where pre('validate') was not helping, hence I used pre('save'). I read that some of the operations are executed directly on the database and hence mongoose middleware will not be called. I changed my route endpoint which will trigger .pre('save'). I took Lodash to parse through the body and update only the field that is passed to the server.

router.post("/", async function(req, res, next){
    try{
        const body = req.body;
        const doc  = await MyModel.findById(body._id);
        _.forEach(body, function(value, key) {
            doc[key] = value;
        });

        doc.save().then( doc => {
            res.status(200);
            res.send(doc);
            res.end();
        });

    }catch (err) {
        res.status(500);
        res.send({error: err.message});
        res.end();
    }

});
Regression answered 21/2, 2020 at 20:11 Comment(0)
L
0

my problem was that i put it after Model initialization

Lynching answered 25/5 at 14:43 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Lyingin

© 2022 - 2024 — McMap. All rights reserved.