Sorry if this is obvious, but I googled for hours and most result is related to sub-document/nested-schema, which is for array of object and not my case.
For simplicity, I just construct a minimal object, but the concept is the same.
I have a mongoose Schema as follow, I want to validate father object by validateFunction, which the validateFunction is just checking if firstName/lastName exists or not:
var PersonSchema = new Schema({
firstName : String,
lastName : String,
father : {
firstName: String,
lastName: String
},
mother : {
firstName: String,
lastName: String
}
};
I have tried
var PersonSchema = new Schema({
firstName : String,
lastName : String,
father : {
type: {
firstName: String,
lastName: String
},
validate : validateFunction
},
mother : {
firstName: String,
lastName: String
}
};
Which seems to work, but after reading Mongoose Schema Type, it seems the type is not a valid type in mongoose.
Can someone point me the proper way to add validation on a child object(father)?
Note: I have check this SO which is similar, but I don't want to store 'father' in a separate collection as the Person is the only object that use 'father', so I think father so be inside 'Person' object.
Edit: I have tested #Azeem suggestion with the following code:
var log = function (val) {
console.log(val);
return true ;
}
var validateFunction = function (val) {
if (typeof val === 'undefined') {
return false;
}
console.log(typeof val, val);
return true;
}
var many = [
{ validator: log, msg: 'fail to log' },
{ validator: validateFunction, msg: 'object is undefined' }
];
var PersonSchema = new Schema({
firstName : String,
lastName : String,
father : {
firstName: String,
lastName: {type : String }
validate : many // following http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate
},
mother : {
firstName: String,
lastName: String
}
});
var PersonModel = mongoose.model("PersonTest", PersonSchema);
var josephus = new PersonModel({firstName:'Josephus', father:{lastName:null}});
josephus.save(function(error) {
console.log("testing", error);
})
and got error
***/index.js:34
validate : many
^^^^^^^^
SyntaxError: Unexpected identifier
at Module._compile (module.js:439:25)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:945:3
if the schema is changed to the following one, it works (prove of validate function running properly)
var PersonSchema2 = new Schema({
firstName : String,
lastName : String,
father : {
firstName: String,
lastName: {type : String ,validate : many}
},
mother : {
firstName: String,
lastName: String
}
});
I have a example where i put small validation in my moongoose schema, hope it may help.
var UserType = require('../defines/userType');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//Schema for User
var UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String
},
password: {
type: String,
required: true
},
dob: {
type: Date,
required: true
},
gender: {
type: String, // Male/Female
required: true
default: 'Male'
},
type: {
type: Number,
default: UserType.User
},
address:{
streetAddress:{
type: String,
required: true
},
area:{
type: String
},
city:{
type: String,
required: true
},
state:{
type: String,
required: true
},
pincode:{
type: String,
required: true
},
},
lastLocation: {
type: [Number], // [<longitude>, <latitude>]
index: '2d', // create the geospatial index
default: [77.2166672, 28.6314512]
},
lastLoginDate: {
type: Date,
default: Date.now
},
});
//Define the model for User
var User;
if(mongoose.models.User)
User = mongoose.model('User');
else
User = mongoose.model('User', UserSchema);
//Export the User Model
module.exports = User;
Like this, you can add further validation. In your mongo query, just check
db.collection.validate(function(err){
if(err){
console.log(err); //if your validation fails,you can see the error.
}
});
Try this
var PersonSchema = new Schema({
firstName : String,
lastName : String,
father : {
firstName: String,
lastName: String
validate : validateFunction
},
mother : {
firstName: String,
lastName: String
}
};
Required Validators On Nested Objects
mongoose docs actually suggest to use nested schema, so we can do validation on an object.
var ParentSchema = new Schema({
firstName: String,
lastName: String
});
var PersonSchema = new Schema({
firstName : String,
lastName : String,
father : {
type: ParentSchema,
validate : validateFunction
},
mother : {
type: ParentSchema,
validate : validateFunction
}
};
This should do the tricks and do validation.
Related
I am new to Mongoose and would like to know if it is possible to add validators on the fly on some parameters depending on queries. I have for example a schema like below:
var user = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
});
For a simple registration i force users giving the name, the email and the password. The Schema on top is OK. Now later I would like to force users giving the city and the country. Is it possible for example to update a user's document with the parameters city and country on required? I am avoiding to duplicate user schema like below:
var userUpdate = new Schema({
name: { type: String },
email: { type: String },
password: { type: String },
city: { type: String, required: true },
country: { type: String, required: true }
});
What you would need to do in this case is have one Schema and make your required a function which allows null and String:
var user = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
city: {
type: String,
required: function() {
return typeof this.city === 'undefined' || (this.city != null && typeof this.city != 'string')
}
}
});
You can extract this and make it an outside function which then you can use for county etc.
What this does is it makes the field required but also you can set null to it. In this way you can have it null in the beginning and then set it later on.
Here is the doc on required.
As far as I know, no, it is not possible.
Mongoose schema are set on collection, not on document.
you could have 2 mongoose model pointing to the same collection with different Schema, but it would effectively require to have duplicated Schema.
personnally, in your situation, I would create a single home-made schema like data structure and a function who, when feeded with the data structure, create the two version of the Schema.
by example :
const schemaStruct = {
base : {
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
}
addRequired : ["city", "country"]
}
function SchemaCreator(schemaStruct) {
const user = new Schema(schemaStruct.base)
const schemaCopy = Object.assign({}, schemaStruct.base)
schemaStruct.addRequired.forEach(key => {
schemaCopy[key].required = true;
})
const updateUser = new Schema(schemaCopy);
return [user, updateUser];
}
I have a user model schema, a work model schema, and a critique model schema. The relationship between these schema's is a user can submit many works (like blog posts), and can comment/review (which we call critiques) other people's posts (works).
So when a user submits a critique (think of it like a review), this is my post route. I find the work by the id, then create a new critique model object, and pass that to the .create() mongoose function. All goes seemingly well until I hit the foundWork.critiques.push(createdCritique) line. the console log errors out saying:
BulkWriteError: E11000 duplicate key error collection: zapper.critiques index: username_1 dup key: { : null }
Obviously, it is saying that there are two username keys in the objects and they're conflicting with each other, but I'm not familiar enough with this to find the root of the issue and fix it in the mongoose models. The models are below. If anyone could help, that'd be greatly appreciated.
// post route for getting the review
router.post('/:id', isLoggedIn, function(req, res) {
Work.findById(req.params.id, function(err, foundWork) {
if (err) {
console.log(err);
} else {
// create a new critique
var newCritique = new Critique ({
reviewerName: {
id: req.user._id,
username: req.user.username
},
work: {
id: foundWork._id,
title: foundWork.title
},
critique : req.body.critique,
date: Date.now(),
rating: 0
});
// save new critique to db
Critique.create(newCritique, function(err, createdCritique) {
if (err) {
console.log(err)
} else {
console.log("Created critique is ");
console.log(createdCritique);
// push the new critique into array of critiques of the work
foundWork.critiques.push(createdCritique);
// save to db
foundWork.save();
}
});
}
});
User model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var UserSchema = new mongoose.Schema({
firstname: String,
lastname: String,
username: String,
password: String,
email: String,
zip: String,
bio: {
type: String,
default: ''
},
influences: {
type: String,
default: ''
},
favBooks: {
type: String,
default: ''
},
notWriting: {
type: String,
default: ''
},
favHero: {
type: String,
default: ''
},
favVillain: {
type: String,
default: ''
},
works: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Work'
}
],
critiques: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Critique'
}
],
friends: [
{
friendId: String,
friendName : String,
friendPic: String
}
],
friendRequests: [
{
sendingFriendId: String,
sendingFriendName : String,
sendingFriendPic: String
}
],
createdDate: {
type: Date,
default: Date.now
},
lastLogin: {
type: Date,
default: Date.now
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
Work model:
var mongoose = require('mongoose');
var WorkSchema = new mongoose.Schema({
title: String,
genre: String,
workType: String,
length: Number,
ageRange: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
manuscriptText: String,
critiques: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Critique"
}
}
],
ratingNumber: [Number],
ratingSum: {
type: Number,
default: 0
},
date: {
type: Date,
default: Date.now
},
isPublic: {
type: Boolean,
default: true
}
});
module.exports = mongoose.model("Work", WorkSchema);
Critique model:
var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');
var CritiqueSchema = new mongoose.Schema({
reviewerName: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
work: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Work"
},
title: String
},
critique: String,
date: {
type: Date,
default: Date.now
},
rating: [Number]
});
CritiqueSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Critique", CritiqueSchema);
When you create a unique index in MongoDB, the default behavior is that it will index null values also.
This means if you have a document in your collection with a username of null, you can not add another one with a username of null.
What you need is a sparse index which only indexes actual values (and ignores documents with null for that field).
Check this link It shows how to create a sparse index vs "normal" one in mongoose (index: true, vs spare: true). Most of the time you would want sparse indexes.
I want to make the "address" property "null" or "undefined" if I haven't initialized it in mongoose. I have tried to initialize it to a null value but didn't work.
var userSchema = new Schema({
name: { type: String, default: null },
address: {
number: { type: String, default: null },
route: { type: String, default: null },
areaInner: { type: String, default: null },
areaOuter: { type: String, default: null },
country: { type: String, default: null }
}
},{timestamps:true});
var User = mongoose.model('users', userSchema);
var newUser = User({
name: name,
address:null
});
newUser.save();
You have to define an other schema for "address":
var addressSchema = new Schema({
number: {type: String, default: null},
...
});
var userSchema = new Schema({
name: {type: String, default: null},
address: {type: addressSchema, default: null}
});
i've seen many answers to this question here, but i still don't get it (maybe because they use more "complex" examples)...
So what im trying to do is a schema for a "Customer", and it will have two fields that will have nested "subfields", and others that may repeat. here is what i mean:
let customerModel = new Schema({
firstName: String,
lastName: String,
company: String,
contactInfo: {
tel: [Number],
email: [String],
address: {
city: String,
street: String,
houseNumber: String
}
}
});
tel and email might be an array.
and address will not be repeated, but have some sub fields as you can see.
How can i make this work?
const mongoose = require("mongoose");
// Make connection
// https://mongoosejs.com/docs/connections.html#error-handling
mongoose.connect("mongodb://localhost:27017/test", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Define schema
// https://mongoosejs.com/docs/models.html#compiling
const AddressSchema = mongoose.Schema({
city: String,
street: String,
houseNumber: String,
});
const ContactInfoSchema = mongoose.Schema({
tel: [Number],
email: [String],
address: {
type: AddressSchema,
required: true,
},
});
const CustomerSchema = mongoose.Schema({
firstName: String,
lastName: String,
company: String,
connectInfo: ContactInfoSchema,
});
const CustomerModel = mongoose.model("Customer", CustomerSchema);
// Create a record
// https://mongoosejs.com/docs/models.html#constructing-documents
const customer = new CustomerModel({
firstName: "Ashish",
lastName: "Suthar",
company: "BitOrbits",
connectInfo: {
tel: [8154080079, 6354492692],
email: ["asissuthar#gmail.com", "contact.bitorbits#gmail.com"],
},
});
// Insert customer object
// https://mongoosejs.com/docs/api.html#model_Model-save
customer.save((err, cust) => {
if (err) return console.error(err);
// This will print inserted record from database
// console.log(cust);
});
// Display any data from CustomerModel
// https://mongoosejs.com/docs/api.html#model_Model.findOne
CustomerModel.findOne({ firstName: "Ashish" }, (err, cust) => {
if (err) return console.error(err);
// To print stored data
console.log(cust.connectInfo.tel[0]); // output 8154080079
});
// Update inner record
// https://mongoosejs.com/docs/api.html#model_Model.update
CustomerModel.updateOne(
{ firstName: "Ashish" },
{
$set: {
"connectInfo.tel.0": 8154099999,
},
}
);
// address model
var addressModelSchema = new Schema({
city: String,
street: String,
houseNumber: String
})
mongoose.model('address',addressModelSchema ,'address' )
// contactInfo model
var contactInfoModelSchema = new Schema({
tel: [Number],
email: [String],
address: {
type: mongoose.Schema.Type.ObjectId,
ref: 'address'
}
})
mongoose.model('contactInfo ',contactInfoModelSchema ,'contactInfo ')
// customer model
var customerModelSchema = new Schema({
firstName: String,
lastName: String,
company: String,
contactInfo: {
type: mongoose.Schema.Type.ObjectId,
ref: 'contactInfo'
}
});
mongoose.model('customer', customerModelSchema, 'customer')
// add new address then contact info then the customer info
// it is better to create model for each part.
I extends mongoose save function:
User Schema is:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var UserSchema = new mongoose.Schema({
Name: String,
Surname: String,
login: {type: String, required: true},
Password: {type: String, required: true},
email: { type: String, index: { unique: true } },
Age: Number
});
And extended method is:
UserSchema.methods.save = function(okFn, failedFn) {
if (this.isValid()) {
this.__super__(okFn);
} else {
failedFn();
}
};
And on 'save' try it gives me error:
TypeError: Object { object fields and values } has no method '__super__'