Validateur asynchrone avec Mongoose

Comment valider un champ de son modèle en asynchrone avant de l’enregistrer ?

Prenons le cas suivant : on a un modèle Mobilhomes et un modèle Marque.  On utilise mongoose pour enregistrer un mobilhome.

Ici, je n’utilise pas les ref dans le schéma, mais on pourrait le faire (cf http://mongoosejs.com/docs/populate.html).

var MobilHomeSchema = Schema({
  _id: Schema.Types.ObjectId,
  marque: {
    _id: Schema.Types.ObjectId,
    lib: String
  }
});

var MarqueSchema = Schema({
  _id: Schema.Types.ObjectId,
  lib: String
});

Le souci est que lorsqu’on enregistre un mobilhome avec un ID de marque « bidon », pour valider que l’ID existe bien et qu’un petit malin ne s’amuse pas à essayer d’enregistrer un ID bidon, on veut valider la donnée.

Il faut donc aller chercher dans la collection Marque, si l’ID existe ou non.

On a créé un validateur « custom » mongoose, qui se déclenche au validate(). Dans ce validateur, on fait un marque.findById(ID_A_VALIDER, callback) => ARGH ! on ne récupère le résultat du findById qu’en asynchrone, dans le callback, trop tard pour le validateur mongoose ! En gros, le validateur aurait besoin de savoir, en synchrone, si l’ID existe ou non….

var MobilHomeSchema = Schema({
  _id: {
      type: Schema.Types.ObjectId,
      validate: myCustomValidatorFunction
  marque: {
    _id: Schema.Types.ObjectId,
    lib: String
  }
});

function myCustomValidatorFunction(val){
   // supposons que Marque soit défini, que ce soit un modèle mongoose
   Marque.findById(val, function(err, item){
     if (err) handleError(err);

     // et là, qu'est-ce qu'on fait ?? on retourne TRUE ou FALSE ?
     // ben non... c'est trop tard...
     // 'suite' a déjà été affiché ! Vive l'asynchrone...

     // le code suivant ne sert évidemment à rien :(
     return (item != null);
   });

   console.log('suite');
}

La solution est donc, comme assez souvent avec l’asynchrone, que la librairie nous a prévu le coup ! Le custom validator peut attendre un second paramètre, qui est un callback que mongoose utilise pour traiter des validateurs asynchrones. Bon c’est écrit noir sur blanc dans la doc, mais ça mérite un petit exemple (http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate) :

function myCustomValidatorFunction(val, respond){
   // supposons que Marque soit défini, que ce soit un modèle mongoose
   Marque.findById(val, function(err, item){
     if (err) handleError(err);

     // ici, on appelle le callback avec un booléen en paramètre, résultat de notre validateur
     respond(item != null);
   });
}