Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What to do with ENUM values in Mongoose?

Background

I have a Mongoose schema that defines a set of possible values a given object can have.

const mongoose = require("mongoose");

const COUNTRIES = ["ES", "PT", "US", "FR", "UK"];
const GENDERS = ["M", "F"];

const surveySchema = {
    subject: { type: String, required: true },
    country: { type: String, enum: COUNTRIES },
    target: {
        gender: { type: String, enum: GENDERS }
    }
};

module.exports = new mongoose.Schema(surveySchema);;
module.exports.modSchema = surveySchema;

Why I don't like ENUM

I don't personally like ENUM values because if I add another value to the ENUM, I have to recompile the entire application again and deploy.

I guess that with an ENUM such as gender, that will never change, this is not a problem.

However, with countries, my SQL side tells me I should store them because if you have a growing business, you are likely to expand to other countries.

Problem

My problem here is that I don't know how to tell Mongoose, at a schema level, that the only allowed values for the countries have to be ["ES", "PT", "US", "FR", "UK"].

I guess I could create a collection countries, but then I lack the knowledge on how I would connect them. Would I have to use async validators?

How would you deal with an ENUM that can change?

like image 604
Flame_Phoenix Avatar asked Apr 01 '17 16:04

Flame_Phoenix


People also ask

What does enum do in Mongoose?

Mongoose String and Number types have an enum validator. The enum validator is an array that will check if the value given is an item in the array. If the value is not in the array, Mongoose will throw a ValidationError when you try to save() .

Does MongoDB support enums?

Enumerations, also known as enums, are not supported natively in the Java SDK.

Does JSON support enum?

JSON has no enum type. The two ways of modeling an enum would be: An array, as you have currently. The array values are the elements, and the element identifiers would be represented by the array indexes of the values.

What does trim do in Mongoose?

Definition. Removes whitespace characters, including null, or the specified characters from the beginning and end of a string. The string to trim.


2 Answers

You can use admin panel to add more country to the country collection. As you are saying that COUNTRIES array can grow, you can use another collection to add more countries on demand from admin panel.

And when you are going to add/save a new record into the survey you can trigger a pre-save hook to mongo for validation.

suppose we have another schema for countries like this.

{
 countries: [String]
}

Here is a sample code for the scenario.

const mongoose = require("mongoose");

const GENDERS = ["M", "F"];

const surveySchema = {
    subject: { type: String, required: true },
    country: { type: String},
    target: {
        gender: { type: String, enum: GENDERS }
    }
};

var Survey = new mongoose.Schema(surveySchema);

Survey.pre('save',function(next){
  var me = this;
  CountryModel.find({},(err,docs)=>{
    (docs.countries.indexOf(me.country) >=0) ? next() : next(new Error('validation failed'));
  });
});

This way you can handle dynamic country add without changing the country array and redeploying your whole server.

USING CUSTOM VALIDATOR

const mongoose = require("mongoose");

const GENDERS = ["M", "F"];

const surveySchema = {
        subject: {
            type: String,
            required: true
        },
        country: {
            type: String,
            validate: {
                isAsync: true,
                validator: function(arg, cb) {
                    CountryModel.find({}, (err, docs) => {
                                if (err) {
                                    cb(err);
                                } else {
                                    cb(docs.countries.indexOf(arg) >= 0);
                                }
                            }
                        },
                        message: '{VALUE} is not a valid country'
                }
            },
            target: {
                gender: { type: String, enum: GENDERS }
            }
        };

you will get an error while saving the survey data in the callback ..

ServeyModel.save((err,doc)=>{
if(err){
console.log(err.errors.country.message);
//Error handle
}else {
//TODO
}
});
like image 91
Shawon Kanji Avatar answered Nov 14 '22 23:11

Shawon Kanji


Just wanted to add one thing, @Shawon's answer is great. Do know that there are two ways to make a validator async. One is shown above where if you specify the isAsync flag and then Mongoose will pass that callback into your validator function to be called at the end of your validation, but you should only use that flag if you're not returning a promise. If you return a promise from your custom validator, Mongoose will know it is async and the isAsync flag is no longer required.

The Mongoose async custom validator doc is found here which shows these both examples really well, great docs. http://mongoosejs.com/docs/validation.html#async-custom-validators

like image 37
Kyle Zinter Avatar answered Nov 15 '22 00:11

Kyle Zinter