JKunstwald
JKunstwald

Reputation: 590

Mongoose .pre('save') does not trigger

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.

Upvotes: 25

Views: 23425

Answers (6)

RuslanNode
RuslanNode

Reputation: 1

my problem was that i put it after Model initialization

Upvotes: 0

ZAFAR HUSSAIN
ZAFAR HUSSAIN

Reputation: 181

The short solution is use findOne and save

const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();

Upvotes: 2

Abdus
Abdus

Reputation: 320

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

Upvotes: 8

Emanuel
Emanuel

Reputation: 3267

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
  }

Upvotes: 2

Anees Hameed
Anees Hameed

Reputation: 6544

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();
    }

});

Upvotes: 0

victorkt
victorkt

Reputation: 14552

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();
  });
});

Upvotes: 71

Related Questions