Comment faire des validations avant d'enregistrer les données éditées dans la mangouste?
Par exemple, si sample.name
existe déjà dans la base de données, l'utilisateur recevra une sorte d'erreur, quelque chose comme ça, voici mon code ci-dessous
//Post: /sample/edit
app.post(uri + '/edit', function (req, res, next) {
Sample.findById(req.param('sid'), function (err, sample) {
if (err) {
return next(new Error(err));
}
if (!sample) {
return next(new Error('Invalid reference to sample information'));
}
// basic info
sample.name = req.body.supplier.name;
sample.tin = req.body.supplier.tin;
// contact info
sample.contact.email = req.body.supplier.contact.email;
sample.contact.mobile = req.body.supplier.contact.mobile;
sample.contact.landline = req.body.supplier.contact.landline;
sample.contact.fax = req.body.supplier.contact.fax;
// address info
sample.address.street = req.body.supplier.address.street;
sample.address.city = req.body.supplier.address.city;
sample.address.state = req.body.supplier.address.state;
sample.address.country = req.body.supplier.address.country;
sample.address.Zip = req.body.supplier.address.Zip;
sample.save(function (err) {
if (err) {
return next(new Error(err));
}
res.redirect(uri + '/view/' + sample._id);
});
});
});
En règle générale, vous pouvez utiliser validation mangouste mais comme vous avez besoin d'un résultat asynchrone (requête db pour les noms existants) et que les valideurs ne prennent pas en charge les promesses (d'après ce que je peux dire), vous devrez créer votre propre fonction et passer un rappel. Voici un exemple:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
mongoose.connect('mongodb://localhost/testDB');
var UserSchema = new Schema({
name: {type:String}
});
var UserModel = mongoose.model('UserModel',UserSchema);
function updateUser(user,cb){
UserModel.find({name : user.name}, function (err, docs) {
if (docs.length){
cb('Name exists already',null);
}else{
user.save(function(err){
cb(err,user);
});
}
});
}
UserModel.findById(req.param('sid'),function(err,existingUser){
if (!err && existingUser){
existingUser.name = 'Kevin';
updateUser(existingUser,function(err2,user){
if (err2 || !user){
console.log('error updated user: ',err2);
}else{
console.log('user updated: ',user);
}
});
}
});
MISE À JOUR: Une meilleure façon
Le pré crochet semble être un endroit plus naturel pour arrêter la sauvegarde:
UserSchema.pre('save', function (next) {
var self = this;
UserModel.find({name : self.name}, function (err, docs) {
if (!docs.length){
next();
}else{
console.log('user exists: ',self.name);
next(new Error("User exists!"));
}
});
}) ;
MISE À JOUR 2: validateurs personnalisés asynchrones
Il semble que mangouste supporte désormais les validateurs personnalisés asynchrones, ce qui serait probablement la solution naturelle:
var userSchema = new Schema({
name: {
type: String,
validate: {
validator: function(v, cb) {
User.find({name: v}, function(err,docs){
cb(docs.length == 0);
});
},
message: 'User already exists!'
}
}
});
Une autre façon de continuer avec l'exemple @nfreeze utilisé est cette méthode de validation:
UserModel.schema.path('name').validate(function (value, res) {
UserModel.findOne({name: value}, 'id', function(err, user) {
if (err) return res(err);
if (user) return res(false);
res(true);
});
}, 'already exists');
Voici une autre façon d'accomplir cela en moins de code.
MISE À JOUR 3: Statique de classe de modèle asynchrone
Similaire à l'option 2, cela vous permet de créer une fonction directement liée au schéma, mais appelée à partir du même fichier à l'aide du modèle.
model.js
userSchema.statics.updateUser = function(user, cb) {
UserModel.find({name : user.name}).exec(function(err, docs) {
if (docs.length){
cb('Name exists already', null);
} else {
user.save(function(err) {
cb(err,user);
}
}
});
}
Appel à partir d'un fichier
var User = require('./path/to/model');
User.updateUser(user.name, function(err, user) {
if(err) {
var error = new Error('Already exists!');
error.status = 401;
return next(error);
}
});
En plus des exemples déjà publiés, voici une autre approche utilisant express-async-wrap et des fonctions asynchrones (ES2017).
Routeur
router.put('/:id/settings/profile', wrap(async function (request, response, next) {
const username = request.body.username
const email = request.body.email
const userWithEmail = await userService.findUserByEmail(email)
if (userWithEmail) {
return response.status(409).send({message: 'Email is already taken.'})
}
const userWithUsername = await userService.findUserByUsername(username)
if (userWithUsername) {
return response.status(409).send({message: 'Username is already taken.'})
}
const user = await userService.updateProfileSettings(userId, username, email)
return response.status(200).json({user: user})
}))
UserService
async function updateProfileSettings (userId, username, email) {
try {
return User.findOneAndUpdate({'_id': userId}, {
$set: {
'username': username,
'auth.email': email
}
}, {new: true})
} catch (error) {
throw new Error(`Unable to update user with id "${userId}".`)
}
}
async function findUserByEmail (email) {
try {
return User.findOne({'auth.email': email.toLowerCase()})
} catch (error) {
throw new Error(`Unable to connect to the database.`)
}
}
async function findUserByUsername (username) {
try {
return User.findOne({'username': username})
} catch (error) {
throw new Error(`Unable to connect to the database.`)
}
}
// other methods
export default {
updateProfileSettings,
findUserByEmail,
findUserByUsername,
}
Ressources
Si vous recherchez par un index unique, utiliser UserModel.count peut en fait être meilleur pour vous que UserModel.findOne car il retourne le document entier (c'est-à-dire qu'il fait une lecture) au lieu de renvoyer juste un entier.
Pour tous ceux qui tombent sur cette ancienne solution. Il y a une meilleure façon à partir des documents mangouste .
var s = new Schema({ name: { type: String, unique: true }});
s.path('name').index({ unique: true });