4

I want to create 'games' which each have their own unique access 'code'. The code is required in the schema, and I need to generate a code each time a new game is created.

I thought schema.pre('init') would be a good place to generate this access code:

GameSchema.pre('init', function(next) {
    // Code generation logic happens here
    this.code = myNewlyGeneratedCode
    next()
}

Unfortunately, this returns an error message: ValidationError: Game validation failed: code: Path 'code' is required.

Why doesn't this work? Do I have to just create a code before I instantiate a new game?

Delta_HF
  • 43
  • 1
  • 3
  • "init" event is called when you're *retrieving* a document. You need `.pre('save')` instead. – JJJ Apr 11 '18 at 07:15
  • I was avoiding `.pre('save')` because I didn't want to re-generate a game code every time I save changes to my game document. As mentioned in my comment below, I think `.pre('validate')` is what I'm looking for. – Delta_HF Apr 11 '18 at 21:50
  • "validate" is also called every time changes are made to the document so you're not avoiding that problem with it. – JJJ Apr 12 '18 at 05:43
  • Ah, yes, good point. I guess I need to use `this.isNew` inside `pre-save` as suggested by lineus below. It just feels like Mongoose should have better support for this kind of thing; seems like it would be very common. – Delta_HF Apr 12 '18 at 06:39
  • After further testing, I found I couldn't use `pre-save` because MongoDB still complained the `code` path is required. I ended up using `this.isNew` in `pre-validate` to get the job done. – Delta_HF Apr 13 '18 at 05:16
  • You shouldn't use a `required` attribute in the schema for data that's filled in automatically. In fact the cleanest solution would be to add a `default` value to the schema that creates the code in a function. – JJJ Apr 13 '18 at 05:32

1 Answers1

11

As mentioned in the comments, pre('save') is the middleware that runs before your document gets stored in the db. pre('init') gets called on your documents when they are returned from mongodb queries.

The easiest way to demonstrate the order of document middleware is with a simple example:

49768723.js

#!/usr/bin/env node
'use strict';

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
const Schema = mongoose.Schema;

var count = 0;

const schema = new Schema({
  name: String
});

function log(str) {
  console.log(`${++count}: ${str}`);
}

schema.pre('save', function () {
  log('pre-save');
});

schema.pre('init', function () {
  log('pre-init');
});

schema.post('save', function () {
  log('post-save');
});

schema.post('init', function () {
  log('post-init');
});

schema.pre('validate', function () {
  log('pre-validate');
});

schema.post('validate', function () {
  log('post-validate');
});

schema.pre('remove', function () {
  log('pre-remove');
});

schema.post('remove', function () {
  log('post-remove');
});


const Test = mongoose.model('test', schema);

const test = new Test({ name: 'Billy' });

async function main() {
  await test.save();
  log('saved');
  await Test.findOne({ _id: test.id });
  log('found');
  await test.remove();
  log('removed');
  return mongoose.connection.close();
}

main();

output

stack: ./49768723.js
1: pre-validate
2: post-validate
3: pre-save
4: post-save
5: saved
6: pre-init
7: post-init
8: found
9: pre-remove
10: post-remove
11: removed
stack:
lineus
  • 698
  • 4
  • 7
  • Great example, thanks! It looks like `pre-validate` is what I should be using, because I don't want to re-generate the code every time I `save` changes to the game document. – Delta_HF Apr 11 '18 at 21:48
  • 2
    You can use a test like ‘ if (this.isNew) { this.code = gencode() }’ in the pre save hook to generate the code also. This.isNew will only be true on the first save (unless you explicitly set it again later). I think this strategy is just slightly cleaner since the intent of your hook is to ‘save’ a special property rather than validate something. – lineus Apr 12 '18 at 04:31
  • I would like to ask, what is the logic behind this order of execution, I am getting confused by terminology, normally once think "init" as the very first thing that happens in most flows, and here first both pre and post save finish and later pre and post init are executed . – gaurav1999 Sep 07 '20 at 17:18