I've been learning Node.JS as of recently, and I'm currently using Sequelize.
I have a problem with the update method; it updates just fine, but when I input values that should be incompatible with an attribute's datatype, it still passes it, and updates it in the database.
For exemple: in Postman, when I try to update a record's "completed" attribute with a string, it gets updated even though the datatype was specified as a Boolean, and delivers no error message(request status is 200).
Here is the code:
todo model:
module.exports = function (sequelInst, DataTypes){
return sequelInst.define('todo', {
description: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [1, 250]
}
},
completed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
}
});
};
server.js:
...
app.put('/todos/:id', function(req,res){
var body =_.pick(req.body, 'description', 'completed');
var attributes = {};
var paramId = parseInt(req.params.id, 10);
if( body.hasOwnProperty('completed')){
attributes.completed = body.completed;
}
if( body.hasOwnProperty('description')) {
attributes.description = body.description;
}
db.todo.findById(paramId)
.then(function(todo){ // First Promise Chain
if(todo){
return todo.update(attributes);
}
else{
res.status(404).send("No todo corresponding to id");
}
}, function () {
res.status(500).send("Server Error");
})
.then(function(todo) { // Second Promise Chain
res.send(todo);
}, function (e){
res.status(400).json(e);
});
});
Instance.update does not validate based on type.
Since you are not getting an error, your probably using SQLite or another storage which does not have strict validation on types at the database level either.
You need to add your own validator. If you do it like this:
completed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true
}
}
You will get the following error:
Unhandled rejection SequelizeValidationError: Validation error: Validation isBoolean failed
However it looks like this validation is deprecated:
validator *deprecated* you tried to validate a boolean but this library (validator.js) validates strings only. Please update your code as this will be an error soon. node_modules/sequelize/lib/instance-validator.js:276:33
This will work:
var _ = require('lodash');
validate: {
isBoolean: function (val) {
if (!_.isBoolean(val)) {
throw new Error('Not boolean.');
}
}
}
Will give you an error:
Unhandled rejection SequelizeValidationError: Validation error: Not boolean.
Related
Hello, I have a mongoose schema for a slug. I want to check uniqueness of it
slug: {
type: String,
default: "",
trim: true,
validate: {
validator: async function (value) {
const user = await this.model.findOne({ slug: value });
console.log(user);
console.log(this);
if (user) {
if (this.id === user.id) {
return true;
}
return false;
}
return true;
},
message: (props) => "This slug is already in use",
},
},
this validation is working fine when inserting a new document but in updating case, I want to compare it with all other fields in the schema other than itself. how could I do that
I have also added runValidators to check validation when updating also
CMS.pre("findOneAndUpdate", function () {
this.options.runValidators = true;
});
if you can suggest a better way of checking slug uniqueness in mongoose when inserting and updating
Thanks in advance
Why are you using a validator? Why not just ensure that the slug is unique by defining an index?
const User = new Schema({
slug: {
type: String,
default: "",
trim: true,
unique: true,
}
});
You will need to catch the error though when attempting to insert an already existing user, since the the unique option is not a validator. See: How to catch the error when inserting a MongoDB document which violates an unique index?
Reference:
https://mongoosejs.com/docs/faq.html#unique-doesnt-work
https://docs.mongodb.com/manual/core/index-unique/
NOTE: there's an edit at the bottom of the question:
Can I check the database for uniqueness using either a custom validator or a pre hook in a Mongoose.js model file. I am aware that I can check it in the controller, but I'd rather put it in the model file with the rest of the validators just for consistency.
I am also aware there is an npm package called mongoose-unique-validator that does this but I'm no fan of installing a library to do what should be one to five lines of code tops.
Mongoose also has a "unique" property that will throw an error if the item is not unique. But their documents clearly state this is not a validator. And the error it throws does not get routed the same as the validation errors.
Here is the relevant parts of the model file. This will check the db and if there is no dup then it creates the article but if there is a dup it throws an error but not a validation error which is what I want. If I simply return false if there is a dup it just ignores the validation and creates the duplicate article. This is no doubt related to Promises/Async. Here are the relevant Mongoose docs https://mongoosejs.com/docs/validation.html#async-custom-validators. And they talk about how the unique property is not a validator https://mongoosejs.com/docs/faq.html.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const articleSchema = new Schema({
title: {
type: String,
required: [true, "Title is required"],
// unique: true,
// isAsync: true,
validate: {
validator: function(value) {
this.constructor.findOne({title: value}, (err, article) => {
if (err || !article) {
return true;
} else {
// return false;
throw new Error('Duplicate');
}
});
},
message: (props) => `Title "${props.value}" is already in use.`
},
},
content: { type: String, required: true }
});
EDIT: I figured this out, but it only works when creating a new article, not on updates. So the question is still open but the focus is on how to get it to work on updates. On update Mongoose does not treat "this" as the document object like it does on create. Instead "this" is the request object, and "this.constructor.findOne()" throws the error "this.constructor.findOne is not a function". Here's the revised validator:
title: {
type: String,
required: [true, "Title is required"],
isAsync: true,
validate: {
validator: async function(value) {
const article = await this.constructor.findOne({title: value});
if (article) {
throw new Error(`${value} is already in use.`);
}
}
}
}
Your validator function will only run the script and it not pass any callback or promise to mongoose, so mongoose assume that the validator return true and continue the process.
According to the document, you should return promise or use callback.
Promise:
validator: function(value) {
var here = this;
return new Promise(function(resolve, reject) {
here.constructor.findOne({title: value}, (err, article) => {
if (err || !article) {
resolve(true);
} else {
resolve(false);
}
});
})
}
Callback: (need to set isAsync: true)
validator: function(value, cb) {
this.constructor.findOne({title: value}, (err, article) => {
if (err || !article) {
cb(true);
} else {
cb(false, "Content is used");
}
});
}
When i throw an error from the lifeCycle fn "beforeCreate" of any model, blueprint (default api of sails) not throw the error to the request.
When some model validation not pass, blueprint throw error 400 and show error but when i throw an error from beforeCreate, this not throw 400 and not throw the same format error and i need the same error response in the controller (blueprint)
im using Sailsjs 1.0.1
//models/item.js
module.exports = {
attributes: {
nombre: { type: 'string', required: true },
empresa: { model: 'empresa', required: true },
},
beforeCreate: async (values, proceed) => {
let nameUsed = await Item.count({ nombre: values.nombre, empresa: values.empresa });
if (nameUsed > 0) return proceed('ERROR, NAME ALREADY USED FOR THE CURRENT EMPRESA')
return proceed()
}
};
I have a schema with several required fields. When I save a document with a published:false prop, I want to not run any validation and just save the document as is. Later, when published:true, I want to run all the validation.
I thought this would work:
MySchema.pre('validate', function(next) {
if(this._doc.published === false) {
//don't run validation
next();
}
else {
this.validate(next);
}
});
But this isn't working, it returns validation errors on required properties.
So how do I not run validation in some scenarios and run it in others? What's the most elegant way to do this?
Please try this one,
TagSchema.pre('validate', function(next) {
if (!this.published)
next();
else {
var error = new mongoose.Error.ValidationError(this);
next(error);
}
});
Test schema
var TagSchema = new mongoose.Schema({
name: {type: String, require: true},
published: Boolean,
tags: [String]
});
With published is true
var t = new Tag({
published: true,
tags: ['t1']
});
t.save(function(err) {
if (err)
console.log(err);
else
console.log('save tag successfully...');
});
Result:
{ [ValidationError: Tag validation failed]
message: 'Tag validation failed',
name: 'ValidationError',
errors: {} }
With published is false, result is
save tag successfully...
I need to create an index in MongoDB to store unique slugs.
I use this code to generate the index:
this._db = db;
this._collection = this._db.collection("Topics");
this._collection.ensureIndex( { slug: 1 }, { unique: true });
But when I run my tests it fails on the "beforeEach": (I'm using mongo-clean NPM)
beforeEach(function (done) {
clean(dbURI, function (err, created) {
db = created;
instance = topicManager(db);
done(err);
});
});
Saying:
Uncaught Error: Cannot use a writeConcern without a provided callback
What am I doing wrong? (if I comment the ensureIndex everything works)
As the response implies, you might need to provide a callback function to your call to ensureIndex:
this._db = db;
this._collection = this._db.collection("Topics");
this._collection.ensureIndex( { slug: 1 }, { unique: true }, function(error) {
if (error) {
// oops!
}});
ensueIndex() is now deprecated in v^3.0.0
For those who use ^3.0.0:
Deprecated since version 3.0.0: db.collection.ensureIndex() is now an alias for db.collection.createIndex().
https://docs.mongodb.com/manual/core/index-unique/#index-type-unique
Example:
db.members.createIndex( { "user_id": 1 }, { unique: true } )