Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Require One of Several Columns on Mongoose.js

Is there any way to require only one (or more than one, but not all) of several columns in a single collection in Mongoose.js? In my case, I am using Passport and want my user to sign up via one of the providers I provide, or make his/her own. However, I do not want to require the user to sign up via any one provider, but rather whichever one he/she wishes.

Here is a sample schema, from the scotch.io tutorial on Passport (NOTE: This is an example. I am not going to use it in my app, but may use something like it):

// app/models/user.js
// load the things we need
var mongoose = require('mongoose');
var bcrypt   = require('bcrypt-nodejs');

// define the schema for our user model
var userSchema = mongoose.Schema({

    local            : {
        email        : String,
        password     : String,
    },
    facebook         : {
        id           : String,
        token        : String,
        email        : String,
        name         : String
    },
    twitter          : {
        id           : String,
        token        : String,
        displayName  : String,
        username     : String
    },
    google           : {
        id           : String,
        token        : String,
        email        : String,
        name         : String
    }

});

// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if password is valid
userSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};

// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);

How do I make it required that at least one of the objects local, facebook, twitter, or google is specified (not null, not undefined, etc.) before saving the document, without making any single one required (and the other ones not required) or making all of them required? In terms of the app, this would make the user be required to sign up for the first time via a username & password; a Twitter or Facebook OAuth account, or a Google+ OpenID account. However, the user would not be tied to any one provider, so he/she would not have to sign up via a username & password, but nor would he/she have to sign up via a social networking account if that's not his/her thing.

like image 920
trysis Avatar asked Nov 05 '14 19:11

trysis


1 Answers

I'd try using a global pre-validation hook:

const providers = ['google', 'twitter', 'facebook', 'local'];

userSchema.pre('validate', function(next) {
 let hasProvider = false;

 // not sure if this is needed, but sometimes, the scoping is messed up
 const that = this;

 // you should add more validations, e.g. for fields, too
 hasProvider = providers.some(provider => that.hasOwnProperty(provider));

 return (hasProvider) ? next() : next(new Error('No Provider provided'));
});

Note: This only works, if the pre-validation hook is actually being called. If you only use .save() you should be fine according to the docs.:

The save() function triggers validate() hooks, because mongoose has a built-in pre('save') hook that calls validate(). This means that all pre('validate') and post('validate') hooks get called before any pre('save') hooks.

If you use a function that circuments the validation, this might lead to problems. Check https://mongoosejs.com/docs/validation.html for more info!

like image 164
BenSower Avatar answered Oct 03 '22 23:10

BenSower