Mongoose subdocument validation based on required - node.js

TL;DR: how to make custom-type field required in one case (and run validations for subdocument) and not required in other case (without validation for subdocument)?
I have an adress schema and models who using this schema (code below).
In one case it is required and in other case it is not. So how to correctly validate an address? All fields except 'apartment' should be required if this field is required and could be empty or valid (for index case) if it's not required.
Is there some options to pass some options to the child schema for this cases or should I make custom validator in each model?
// adress SCHEMA
module.exports = mongoose.Schema({
town: String,
index: {
type: String,
validate: {
validator: function (v) {
return /^\d+$/.test(v)
},
message: 'Invalid index'
}
},
district: String,
street: String,
house: String,
apartment: String
})
// user.js
const Address = require('./address')
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
address: {
type: Address,
required: true // Adress required here
}
})
module.exports = mongoose.model('User', userSchema)
// other.js
const Address = require('./address')
const mongoose = require('mongoose')
const otherSchema = mongoose.Schema({
address: Address // but not here
})
module.exports = mongoose.model('Other', otherSchema)

to make all fields except apartment required, you just use the required attribute, same way you did with address:
town: {type: String, required: true},
district: {type: String, required: true},
street: {type: String, required: true},
house: {type: String, required: true},
apartment: String
If one of the required fields is left empty, there will be an error when using the create method, the error can be handled to return/keep the user to/on the form page and display an error message to inform them they need to fill the required fields
As for validation, you can check this page of the official mongoose docs to see if the Built-In Validators are enough for your purposes or if you do need to use Custom Validators on some of your fields.

Related

Mongoose require a field based on some enum values of another field

I have a Mongoose schema Employee. In that I want to store a field (phone number) related to office for the employee, only if he/she is eligible for office, which is only for two levels "senior" and "c-level".
The schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var EmployeeSchema = new Schema({
name: String,
designation: String,
level: {
type: String,
enum: ["intern", "junior", "mid-level", "senior", "c-level"],
required: true,
},
phoneNo: { type: String, required: true },
officePhoneNo: { type: String, required: true } // How to require only if the level is senior or c-level?,
});
Appreciate your help.
Thanks
In Mongoose you can pass a function in required that can return true/false depending on some condition.
It's also possible to depend required of a field on other fields, which is level in your case. That is, you can optionally required a field. Here's how:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const levels = ["intern", "junior", "mid-level", "senior", "c-level"];
const levelsEligibleForOffice = ["senior", "c-level"];
var EmployeeSchema = new Schema({
name: String,
designation: String,
level: {type: String, enum: levels, required: true},
phoneNo: {type: String, required: true},
officePhoneNo: {type: String, required: isEligibleForOffice}
});
function isEligibleForOffice(){
if(levelsEligibleForOffice.indexOf(this.level) > -1){ //"this" contains the employee document at the time of required validation
return true;
}
return false;
}

Is it possible to add fields not described in Mongoose model?

For example I have schema like this:
let mongoose = require('mongoose');
let carSchema = new mongoose.Schema({
url: String,
unique: {type: String, index: { unique: true }},
number: String,
title: String,
price: String,
});
module.exports = mongoose.model('Car', carSchema);
When I'm creating new instance is it possible to add extra fields without describing them in the model? For example:
data.bpm = {foo: 'bar'}
new CarModel(data).save(function (err) {
if (err) {
dd(err)
}
})
You can use the strict: false option.
Documentation:
The strict option, (enabled by default), ensures that values passed to our model constructor that were not specified in our schema do not get saved to the db.
Your updated schema will look like this:
let carSchema = new mongoose.Schema({
url: String,
unique: {type: String, index: { unique: true }},
number: String,
title: String,
price: String,
}, { strict: false });
You can use type 'Schema.Types.Mixed' for some field in your schema:
let mongoose = require('mongoose');
let carSchema = new mongoose.Schema({
url: String,
...
data: Schema.Types.Mixed
});
And then use .data field as js object.
You can not since mongoose schema will check if that attribute exists or not, but what you can do is, you can add the following attribute to carSchema:
externalData: Object
And you can set that data to be anything you want.

Unable to check whether the value exists inside nested Model in mongoose

I am creating poll app. My schema definitions are as below
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/pollApp');
var userSchema = new mongoose.Schema({
username: { type: String, required: true},
phonenumber: { type: String, required: true, unique: true}
});
var option = new mongoose.Schema({
title: {type: String, required: true},
votes: { type: Number, default: 0 },
voterList: {type: []}
});
var poll = new mongoose.Schema({
question: { type: String, required: true, unique: true},
options: { type: [option], required: true},
showVoters: {type: Boolean, default: false}
});
mongoose.user = mongoose.model('User', userSchema);
mongoose.poll = mongoose.model('Poll', poll);
module.exports = mongoose;
voterList will contain all the voters name.Before adding vote i want to check whether user has already voted for the poll(need to check user exists in each voterList array). How to accomplish this?
If you want unique values in the voterList array, you can use $addToSet for pushing a user in the voterList.
but if you want to do some kind of validation. It is better you do a get query which checks if user is already present in the array.
if yes, throw a message saying user already voted else add the user to voterlist
For checking an user is already present in voterList array, it is very simple actually.
You can use a find query like below:
find({voterList:'585ce839c84f5d3d1ef15d56'})
Even if voterList is an array, mongo will see if the provided value is present in the array or not.

Mongoose result.toObject keep Schema methods

Because I cannot edit properties of a non-lean mongoose result, I've used the result.toObject() statement, but that also means I cannot use the methods defined on my Schema.
Example
// Defining the schema and document methods
const User = new Schema({
email: {type: String, required: true, unique: true},
firstname: {type: String, required: true},
registration_date: {type: Date, default: Date.now, required: true},
insert: {type: String},
lastname: {type: String, required: true}
});
User.methods.whatIsYourFirstName = function () {
return `Hello, my firstname is:${this.firstname}`;
};
After a find:
user = user.toObject();
user.registration_date = moment(user.registration_date);
user.whatIsYourFirstName();
// result in "user.whatIsYourFirstName is not a function"
Is this solvable?
Methods and Models are part of Mongoose, not MongoDB.
Whenever you are calling .toObject() you are being returned an object which is ready for storage in MongoDB.
If you do need to do any sort of value transformation, I'd do it just before you deliver the value to the user. Being a time formatting, if you are building an API, I'd do that in the client; if you are working with templates try transforming the value on the same template.

Add uppercase: true to mongoose embedded document

If I have two schemas, one which will be embedded in the other:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Will embed this in the personSchema below
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
module.exports = mongoose.model("Person", personSchema);
I can't seem to get the uppercase: true to work for embedded documents - no error is thrown, but it simply doesn't uppercase the state property. Or any kind of option like that.
I've been searching the Mongoose docs, but maybe I'm just not finding where it mentions that settings these kinds of additional options on subDocuments won't work.
Up until recently, Mongoose would throw an exception if you tried to directly embed one schema within another like you're doing. It looks like it's partially supported now, but apparently not for cases like this.
You can get this to work by using just the definition object from addressSchema instead of the schema itself in the definition of the address field of personSchema.
var addressObject = {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
};
var addressSchema = new Schema(addressObject);
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressObject
});
Not positive if this is the best way to do it or not, but I added a pre-save hook (per the suggestion of #nbro in the comments) and that seems to be working:
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
addressSchema.pre("save", function (next) {
this.state = this.state.toUpperCase();
next();
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
Update #1:
I seem to be able to find lots of cases of people embedding simple schemas without any additional validation (required: true) or alteration (uppercase: true) occurring. While the above solution does work, it seems kind of unnecessary. What I should probably be doing is just putting in the object literal to embed the info:
var personSchema = new Schema({
...
address: {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
}
});
It seems like the only good reason to use a separate Schema is if you absolutely need the embedded data to have an _id attribute and you don't need to add additional validation or alteration options to any of the properties. If you need an _id, I'm guessing you should probably not be embedding the data, but saving it as a separate object and making a reference.
I'll keep updating this as I discover new information and best practices.
Update #2:
If you want to include validation to the embedded document, such as making the address property required, you're going to have to do it separately, as outlined in this very good blog post about it.

Resources