How do I handle a Unique Field in sails?
Asked Answered
M

6

6

I've defined a unique field in my model but when I tried to test it seems like it's not being checked by sails because I get a Error (E_UNKNOWN) :: Encountered an unexpected error: MongoError: E11000 duplicate key error index: instead a sails ValidationError.

What is the best way to handle a unique field in sails?

// model/User.js
module.exports{
attributes: {
  email: {required: true, unique: true, type: 'email' },
  ....
}
// in my controller
User.create({email: '[email protected]'}).then(...).fail(....)
User.create({email: '[email protected]'}).then(...).fail(// throws the mongo error ) 
// and same goes with update it throws error

Thanks in advance guys.

Mazard answered 4/4, 2014 at 16:41 Comment(3)
can we get some code?Wisla
@Wisla I've updated the question to include some codes. ThanksMazard
@Mazard Please mark your preferred solution as the correct answer.Maggee
M
10

The unique attribute currently only creates a unique index in MongoDB.

You may use the beforeValidate() callback to check for an existing record with that attribute and save the result in a class variable.

This approach makes sure that your model returns a proper validation error which can be evaluated by clients.

var uniqueEmail = false;

module.exports = {


    /**
     * Custom validation types
     */
    types: {
        uniqueEmail: function(value) {
            return uniqueEmail;
        }
    },

    /**
     * Model attributes
     */
    attributes: {
        email: {
            type: 'email',
            required: true,
            unique: true,            // Creates a unique index in MongoDB
            uniqueEmail: true        // Makes sure there is no existing record with this email in MongoDB
        }
    },

    /**
     * Lifecycle Callbacks
     */
    beforeValidate: function(values, cb) {
        User.findOne({email: values.email}).exec(function (err, record) {
            uniqueEmail = !err && !record;
            cb();
        });
    }
}

EDIT

As thinktt pointed out, there was an error in my former solution which rendered the default uniqueEmail value useless since it is defined within the model declaration itself and therefore cannot be referenced within the model code. I've edited my answer accordingly, thanks.

Maggee answered 13/3, 2015 at 12:9 Comment(0)
P
5

You are trying to create two users with the same email address after defining the email as a unique field.

Maybe you can query for a user by that email address - if it exists already - return an error or update that user.

var params = {email: '[email protected]'};

User.findOne(params).done(function(error, user) {

  // DB error
  if (error) {
    return res.send(error, 500);
  }

  // Users exists
  if (user && user.length) {

    // Return validation error here
    return res.send({error: 'User with that email already exists'}, 403.9);
  }

  // User doesnt exist with that email
  User.create(params).done(function(error, user) {

    // DB error
    if (error) {
      return res.send(error, 500);
    }

    // New user creation was successful
    return res.json(user);

  });

});

Sails.js & MongoDB: duplicate key error index

There is also an interesting bit about unique model properties in the Sails.js docs https://github.com/balderdashy/waterline#indexing

EDIT: Pulled from http://sailsjs.org/#!documentation/models

Available validations are:

empty, required, notEmpty, undefined, string, alpha, numeric, alphanumeric, email, url, urlish, ip, ipv4, ipv6, creditcard, uuid, uuidv3, uuidv4, int, integer, number, finite, decimal, float, falsey, truthy, null, notNull, boolean, array, date, hexadecimal, hexColor, lowercase, uppercase, after, before, is, regex, not, notRegex, equals, contains, notContains, len, in, notIn, max, min, minLength, maxLength

Percentile answered 5/4, 2014 at 1:22 Comment(2)
Thanks, Yes I'm thinking of using this method also but I'm just curious if there's a better way that will generate a ValidationError.Mazard
I updated my post to include the list of validations performed by sailsjs. Unique is not one of them - so that is left up to you to interpret the response from mongodb when trying to create a record that contains a unique email.Percentile
J
2

The solutions by @tvollstaedt and David posted work but there is a big issue with this code. I was struggling with it all day so I am putting up this slightly modified answer. I would just comment but I do not have the points yet. I will gladly remove this answer if they can update theirs but I would really like to help someone who was having the same issues I've been having.

The problem with the above code, using the custom validator, is you cannot access the attribute "uniqueEmail" from the properties the way in witch both the previous solutions are trying to do. The only reason it is working in those solutions is because they are inadvertently throwing "uniqueEmail" into the global space.

The following is a slight modification of the tvollstaedt code that does not use the global space. It defines uniqueEmail outside the modual.exports therefore being scoped only to the module but accessible throughout the module.

There may yet be a better solution but this is the best I could come up with with minimal changing of an otherwise elegant solution.

var uniqueEmail = false; 

module.exports = {

  /**
  * Custom validation types
  */
  types: {
    uniqueEmail: function(value) {
      return uniqueEmail;         
    }
  },

  /**
  * Model attributes
  */
  attributes: {
    email: {
      type: 'email',
      required: true,
      unique: true,            // Creates a unique index in MongoDB
      uniqueEmail: true        // Makes sure there is no existing record with this email in MongoDB
     }
   },

  /**
  * Lifecycle Callbacks
  */
  beforeValidate: function(values, cb) {
    User.findOne({email: values.email}).exec(function (err, record) {
      uniqueEmail = !err && !record;
      cb();
    });
  }
};
Jara answered 30/6, 2015 at 3:21 Comment(0)
C
0

@tvollstaedt Your reponse worked like a charm, and by the way is the most elegant way to handle "uniqueness" in sailsjs so far.

Thank you!

Here is my two cents to add custom validation messages using "sails-validation-messages":

module.exports = {
  /* Custom validation types   */
  uniqueEmail: false,
	types: {
		uniqueEmail: function(value) {
			return uniqueEmail;
		}
	},
  attributes: {
  	firstname:{
			type: 'string',
			required: true,
		},
		lastname:{
			type: 'string',
			required: true,
		},
		email:{
			type: 'email',
			required: true,
			unique: true,
			maxLength:50,
			uniqueEmail:true
		},
		status:{
			type: 'string'
		}
  },
  beforeValidate: function(values, cb) {
  	Application.findOne({email: values.email}).exec(function (err, record) {
  		console.log('before validation ' + !err && !record);
  		uniqueEmail = !err && !record;
  		cb();
  	});
  },
  validationMessages: {
  	firstname: {
      required : 'First name is required',
    },
    lastname: {
      required : 'Last name is required',
    },
    email: {
      required : 'Email is required',
      email : 'Enter valid email',
      uniqueEmail: 'Email already registered'
    },
  }
};

Then in the controller you can handle the error like this:

module.exports = {
	create:function(req, res){
    var values = req.allParams();
	Application.create({
	  email:values.email,
	  firstname:values.firstname,
	  lastname:values.lastname,
	  _csrf: values.csrf
	})
	exec(function created (err, values) {
      if(err) {
		console.log(err);
		if(err.invalidAttributes) {
          validator = require('sails-validation-messages');
		  err.invalidAttributes = validator(Application, err.invalidAttributes);
		    return res.negotiate(err);
	      }
	    }
	  });
	}
};

Thanks

Callous answered 17/5, 2015 at 22:36 Comment(0)
S
0

The correct way to do this!!!

module.exports = {
schema: true,
migrate: 'safe',
tableName: 'users',
autoCreatedAt: false,
autoUpdatedAt: false,
adapter: 'mysql',

/**
 * Custom validation types
 */
types: {
    uniquePhone: function(value) {
        return value!=='_unique';
    }
},
attributes: {
    id: {
        type: 'integer',
        primaryKey: true,
        unique: true,
        autoIncrement: true
    },
    email: {
        type: 'email'
    },
    first_name: {
        type: 'string'
    },
    last_name: {
        type: 'string'
    },
    phone: {
        type: 'string',
        required: true,
        uniquePhone: true
    }

},
/**
 * Lifecycle Callbacks
 */
beforeValidate: function(values, cb) {
    Users.findOne({phone: values.phone}).exec(function(err, record) {
        // do whatever you want check against various scenarios
        // and so on.. 
        if(record){
            values.phone='_unique';
        }
        cb();
    });
}

};

By this way we are not breaking concept of validator!

Sol answered 14/5, 2016 at 8:21 Comment(0)
J
0

In the latest version of sails v1.2.7, the callbacks no longer work. If you run into an issue where uniqueness doesn't work on your models - like I did with sails-mongo, you'd need to manually configure it in your controllers.

Here's an example

//SIGNUP
create: async (req, res) => {
    const { name, email, password } = req.body;
    try {
      const userExists = await sails.models.user.findOne({ email });
      if (userExists) {
        throw 'That email address is already in use.';
      }
}
Julide answered 23/7, 2020 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.