schema mongoose Required field if otherfield is true [duplicate] - node.js

I want to use mongoose custom validation to validate if endDate is greater than startDate. How can I access startDate value? When using this.startDate, it doesn't work; I get undefined.
var a = new Schema({
startDate: Date,
endDate: Date
});
var A = mongoose.model('A', a);
A.schema.path('endDate').validate(function (value) {
return diff(this.startDate, value) >= 0;
}, 'End Date must be greater than Start Date');
diff is a function that compares two dates.

You can do that using Mongoose 'validate' middleware so that you have access to all fields:
ASchema.pre('validate', function(next) {
if (this.startDate > this.endDate) {
next(new Error('End Date must be greater than Start Date'));
} else {
next();
}
});
Note that you must wrap your validation error message in a JavaScript Error object when calling next to report a validation failure.

An an alternative to the accepted answer for the original question is:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// schema definition
var ASchema = new Schema({
startDate: {
type: Date,
required: true
},
endDate: {
type: Date,
required: true,
validate: [dateValidator, 'Start Date must be less than End Date']
}
});
// function that validate the startDate and endDate
function dateValidator(value) {
// `this` is the mongoose document
return this.startDate <= value;
}

I wanted to expand upon the solid answer from #JohnnyHK (thank you) by tapping into this.invalidate:
Schema.pre('validate', function (next) {
if (this.startDate > this.endDate) {
this.invalidate('startDate', 'Start date must be less than end date.', this.startDate);
}
next();
});
This keeps all of the validation errors inside of a mongoose.Error.ValidationError error. Helps to keep error handlers standardized. Hope this helps.

You could try nesting your date stamps in a parent object and then validate the parent. For example something like:
//create a simple object defining your dates
var dateStampSchema = {
startDate: {type:Date},
endDate: {type:Date}
};
//validation function
function checkDates(value) {
return value.endDate < value.startDate;
}
//now pass in the dateStampSchema object as the type for a schema field
var schema = new Schema({
dateInfo: {type:dateStampSchema, validate:checkDates}
});

Using 'this' within the validator works for me - in this case when checking the uniqueness of email address I need to access the id of the current object so that I can exclude it from the count:
var userSchema = new mongoose.Schema({
id: String,
name: { type: String, required: true},
email: {
type: String,
index: {
unique: true, dropDups: true
},
validate: [
{ validator: validator.isEmail, msg: 'invalid email address'},
{ validator: isEmailUnique, msg: 'Email already exists'}
]},
facebookId: String,
googleId: String,
admin: Boolean
});
function isEmailUnique(value, done) {
if (value) {
mongoose.models['users'].count({ _id: {'$ne': this._id }, email: value }, function (err, count) {
if (err) {
return done(err);
}
// If `count` is greater than zero, "invalidate"
done(!count);
});
}
}

This is the solution I used (thanks to #shakinfree for the hint) :
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// schema definition
var ASchema = new Schema({
dateSchema : {
type:{
startDate:{type:Date, required: true},
endDate:{type:Date, required: true}
},
required: true,
validate: [dateValidator, 'Start Date must be less than End Date']
}
});
// function that validate the startDate and endDate
function dateValidator (value) {
return value.startDate <= value.endDate;
}
module.exports = mongoose.model('A', ASchema);

Related

Autoincrement with Mongoose

I'm trying to implement an autoicremental user_key field. Looking on this site I came across two questions relevant for my problem but I don't clearly understand what I should do. This is the main one
I have two Mongoose models, this is my ProductsCounterModel.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
});
module.exports = mongoose.model('products_counter', Counter);
and this is the Mongoose model where I try to implement the auto-increment field:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var products_counter = require('./ProductsCounterModel.js');
var HistoricalProduct = new Schema({
product_key: { type: Number },
class: { type: String },
brand: { type: String },
model: { type: String },
description: { type: String }
});
HistoricalProduct.pre("save", function (next) {
console.log("first console log:",products_counter);
var doc = this;
products_counter.findOneAndUpdate(
{ "_id": "product_key" },
{ "$inc": { "sequence_value": 1 } },
function(error, products_counter) {
if(error) return next(error);
console.log("second console log",products_counter);
doc.product_key = products_counter.sequence_value;
next();
});
});
module.exports = mongoose.model('HistoricalProduct', HistoricalProduct);
Following the steps provided in the above SO answer I created the collection products_counter and inserted one document.
The thing is that I'm getting this error when I try to insert a new product:
"TypeError: Cannot read property 'sequence_value' of null"
This are the outputs of the above console logs.
first console log output:
function model (doc, fields, skipId) {
if (!(this instanceof model))
return new model(doc, fields, skipId);
Model.call(this, doc, fields, skipId);
}
second console log:
Null
can you see what I'm doing wrong?
You can run following line in your middleware:
console.log(products_counter.collection.collectionName);
that line will print products_counters while you expect that your code will hit products_counter. According to the docs:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
So you should either rename collection products_counter to products_counters or explicitly configure collection name in your schema definition:
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
}, { collection: "products_counter" });

'this' is undefined in a Mongoose pre save hook [duplicate]

This question already has an answer here:
Mongoose pre/post midleware can't access [this] instance using ES6
(1 answer)
Closed 6 years ago.
I have made a Mongoose database schema for a User entity, and want to add the current date in an updated_at field. I am trying to use the .pre('save', function() {}) callback but every time I run it I get an error message telling me this is undefined. I've also decided to use ES6, which I guess could be a reason for this (everything works though). My Mongoose/Node ES6 code is below:
import mongoose from 'mongoose'
mongoose.connect("mongodb://localhost:27017/database", (err, res) => {
if (err) {
console.log("ERROR: " + err)
} else {
console.log("Connected to Mongo successfuly")
}
})
const userSchema = new mongoose.Schema({
"email": { type: String, required: true, unique: true, trim: true },
"username": { type: String, required: true, unique: true },
"name": {
"first": String,
"last": String
},
"password": { type: String, required: true },
"created_at": { type: Date, default: Date.now },
"updated_at": Date
})
userSchema.pre("save", (next) => {
const currentDate = new Date
this.updated_at = currentDate.now
next()
})
const user = mongoose.model("users", userSchema)
export default user
The error message is:
undefined.updated_at = currentDate.now;
^
TypeError: Cannot set property 'updated_at' of undefined
EDIT: Fixed this by using #vbranden's answer and changing it from a lexical function to a standard function. However, I then had an issue where, while it wasn't showing the error anymore, it wasn't updating the updated_at field in the object. I fixed this by changing this.updated_at = currentDate.now to this.updated_at = currentDate.
the issue is your arrow function uses lexical this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
change
userSchema.pre("save", (next) => {
const currentDate = new Date
this.updated_at = currentDate.now
next()
})
to
userSchema.pre("save", function (next) {
const currentDate = new Date()
this.updated_at = currentDate.now
next()
})

Where to place email validator within project?

I am on day 2 of learning node and Java Script. I have been following basic tutorials and decided to attempt and implement simple email validation into my code.
The problem is i am not sure where to place the code - i have a server.js file that holds all of the CRUD operations and a Mongoose model that which ensures the correct data is entered. Does anyone have any advice as to the best way to validate a user-entered email using this module?
//Email-validation npm module
var validator = require("email-validator");
validator.validate("test#email.com");
//Mongoose model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Tickets = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
address: {
type: String,
required: true
},
price: {
type: Number,
required: true,
min: 1,
max: 100
}
});
module.exports = mongoose.model('Ticket', TicketSchema);
Validate email before saving object. Code should look something like this:
Tickets.pre('save', function (next) {
var ticket = this;
if (!ticket.isModified('email')) {
next();
} else {
var valid = validator.validate(ticket.email);
if(valid) {
next();
} else {
next(valid);
}
}
});

Mongoose: Change validations w.r.t value

I have a sample schema:
{
title: {type: String, required: true},
status: {type: String, enum: ['draft', 'status1', 'status2']},
value1: {type: String, required: true},
value2: {type: String, required: true}
}
Other than required: true, I also have some other validations on value1 and value2, like RegExp
schema.path('title', function (value) { ... });
schema.path('value1', function (value) { ... });
schema.path('value2', function (value) { ... });
I want to run different validation if the status == draft.
For e.g. Only title is required and other validations are ignored.
Currently i'm doing it this way:
schema.method('saveDraft', function(next) {
if (this.status == 'draft') {
schema.set('validateBeforeSave', false);
if (!this.title || this.title.length == 0)
return next(Error("`title` is required"));
}
this.save(next);
});
But this force me to use mydocument.saveDraft(cb).
Is it possible to do using mydocument.save(cb)?
I figured out a way to run different validations depending on current value.
I applied a pre validator hook:
schema.pre('validate', function preValidate(next) {
if (this.status == 'draft') {
// Make all fields optional
_.each(_.keys(schema.paths), function (attr) {
schema.path(attr).required(false);
});
var draftRequiredMap = ['title']; // Based on condition, our required fields are here.
_.each(draftRequiredMap, function (attr) {
schema.path(attr).required(true);
});
}
}
You can:
Overwrite the save method for your schema, it will validate then call the standard save method (that you would have saved somewhere in your code)
Use the pre save hook (assuming you are using mongoose)
schema.pre('save', function(next){
...
next();
});

Using a value of an document as _id when importing this document to MongoDB using NodeJS

I'm trying to import an Object into Mongo. But when I try to use a value of the Object as _id it fails. The error i.e: "[CastError: Cast to ObjectID failed for value "11563195" at path "_id"]" and later "[Error: document must have an _id before saving]"
What am I doing wrong ?
// Read and import the CSV file.
csv.fromPath(filePath, {
objectMode: true,
headers: keys
})
.on('data', function (data) {
setTimeout(function(){
var Obj = new models[fileName](data);
Obj._id = Obj.SOME_ID;
Obj.save(function (err, importedObj) {
if (err) {
console.log(err);
} else {
console.log('Import: OK');
}
});
}, 500);
})
Here is the used Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var SomeSchema = new Schema(
{
SOME_ID: String,
FIELD01: String,
FIELD02: String,
FIELD03: String,
FIELD04: String,
FIELD05: String,
FIELD06: String,
FIELD07: String,
FIELD08: String,
FIELD09: String,
FIELD10: String,
FIELD11: String,
FIELD12: String,
FIELD13: String
},
{
collection: 'SomeCollection'
});
module.exports = mongoose.model('SomeCollection', SomeSchema);
Many thanks for your time and help.
By default mongoose is validating that the _id field is a MongoId. If you want to store something other than a MongoId in the _id field you will need to give the _id field a different type.
var SomeSchema = new Schema({
_id: { type: String, required: true }
}

Resources