I'm working on a project and stuck at a point where the docOne is optional. But if docOne is there, the children of the docOne should be required. Is there any way of achieving this behavior through the schema model? Thank you.
const MySchema = new Schema(
{
name: { type: String },
docOne: {
required: false,
type: {
docTwo: {
required: true,
type: {
isActive: { type: Boolean, required: true },
},
},
},
},
},
{ timestamps: true },
);
try defining the child schema first, and use the child schema in your parent schema.
this way you can make use of sub-documents / embedded-documents where, you can make only required to true.
you can refer https://mongoosejs.com/docs/subdocs.html and https://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html#:~:text=Embedded%20documents%20are%20documents%20with,error%20handling%20is%20a%20snap! for this.
If you are using mongoose Mongoose 5.8.0 or above, just add typePojoToMixed: false to the scheme options and the validations should work fine.
const mySchema = new Schema(
{
// ...Schema definition remains the same
},
{ timestamps: true, typePojoToMixed: false },
);
const myModel = mongoose.model('myModel', mySchema);
const sampleDocument = {
name: 'John Doe',
};
myModel.validate(sampleDocument); // No Error
sampleDocument.docOne = {};
myModel.validate(sampleDocument); // Throws ValidationError for docOne.docTwo
If you are using a lesser version of Mongoose, the other thing that can work is declaring the schema type of the nested objects as a separate schemas:
const docTwoSchema = new Schema({
isActive: { type: Boolean, required: true },
},
// Use the "_id: false" option to avoid an autogenerated _id property
{ _id: false }
);
const docOneSchema = new Schema({
docTwo: {
type: docTwoSchema,
required: true,
}
},
// Use the "_id: false" option to avoid an autogenerated _id property
{ _id: false }
);
const mySchema = new Schema(
{
name: { type: String },
docOne: {
type: docOneSchema,
required: false,
},
},
{ timestamps: true },
);
const myModel = mongoose.model('myModel', mySchema);
const sampleDocument = {
name: 'John Doe',
};
myModel.validate(sampleDocument); // No Error
sampleDocument.docOne = {};
myModel.validate(sampleDocument); // Throws ValidationError for docOne.docTwo
Useful Links
Mongoose SubDocuments
Related
I have tried so many times to add new field to the existing MongoDB document but I failed. I tried following code to do the job but nothing happened.
Here is the User model.
const UserSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true }
);
Here is the code to add new field to the document.
const updateDocument = async () => {
const updatedUser = await User.findOneAndUpdate(
{ _id: "63eb30f466127f7a0f7a9b32" },
{
$set: { lastName: "syed" },
}
);
console.log(updatedUser);
};
updateDocument();
NOTE 1: lastName field does not exist in the MongoDB document and in the UserSchema. I want to add that field to the MongoDB document.
NOTE 2: The same code works when I update the existing field inside the document but it does not work when adding new field.
You need to pass strict:false as an option to findOneAndUpdate.
According to the mongoose doc:
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.
const updatedUser = await User.findOneAndUpdate(
{ _id: "63eb30f466127f7a0f7a9b32" },
{
$set: { lastName: "syed" },
},
{ strict: false }
);
An alternative way is to pass this parameter when you defined the schema:
const UserSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true, strict: false }
);
Trying to reference one collection in mongo via another model's virtual. Currently the only output I'm getting is 'undefined'. Was able to execute this in my main js file as a standalone query but can't figure out how to get this to function as a virtual. Help appreciated.
const mongoose = require('mongoose');
const { stylesList } = require('../styles.js')
const Schema = mongoose.Schema;
const Post = require('./post')
const options = { toJSON: { virtuals: true } };
// options object for the schema, passed in after the actual schema.
const BrandSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
linkedBrands: [String],
styles: [{ type: String, enum: stylesList }],
links: {
homepage: {
type: String,
required: true
},
grailed: {
type: String,
required: true
},
wikipedia: String
},
bannerImage: String,
likes: {
type: Number,
default: 0
}
}, options);
BrandSchema.set('toObject', { getters: true });
BrandSchema.virtual('amountWorn').get(async function() {
const wornPosts = await Post.find({includedBrands: this._id});
return wornPosts.length
});
module.exports = mongoose.model('Brand', BrandSchema)
I am trying the aggregation in mongoose. When I run that aggregation, it show the error. What am I missing?
const data = await Rooms.aggregate([{ $match: { adminID: "1234" } }]);
Error is like that
MongoInvalidArgumentError: Method "collection.aggregate()" accepts at most two arguments
Edit -- code for Rooms Schema
const mongoose = require('mongoose');
const Rooms = new mongoose.Schema(
{adminID: {
type: String,
required: true,
},
roomID: {
type: String,
required: true,
},
roomName: {
type: String,
required: true,
},
users: [
{
id: {
type: String,
required: true,
unique: true,
},
},
],
},
{ timestamps: true } );
module.exports = mongoose.model("rooms", Rooms);
solution 1 : downgrade mongoose to version 5
solution 2 :
const data = await Rooms.aggregate([{ $match: { adminID: "1234" } }],"adminID roomID roomName users");
in new version second argument is selected fields in out put,
or use :
const data = await Rooms.aggregate.match({ adminID: "1234" } )
I have this document in mongo atlas
_id: 5f8939cbedf74e363c37dd86,
firstname: "Person",
lastname: "Person lastname",
sex: "Masculino",
age: "20",
birthDay: 2020-10-07T00:00:00.000+00:00,
vaccines: Array
0:Object
dose: Array
_id: 5f8939cbedf74e363c37dd87
vaccine:5f7023ad96f7ed21e85be521
createdAt:2020-10-16T06:12:27.726+00:00
updatedAt:2020-10-16T06:12:27.726+00:00
1:Object
dose:Array
_id:5f893a9ca98e97188c93fea8
vaccine:5f70259796f7ed21e85be523
2:Object
dose:Array
_id:5f893acda98e97188c93fea9
vaccine:5f7023ad96f7ed21e85be521
This is my mongoose schema
const mySchema = new Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
sex: {
type: String,
required: true,
},
age: {
type: String,
required: true,
},
birthDay: {
type: Date,
required: true,
},
vaccines: [
{
type: new Schema(
{
vaccine: {
type: Schema.ObjectId,
ref: "Vaccine",
},
dose: Array,
},
{ timestamps: true }
),
},
],
});
every time I add a new person the vaccines array gets one new object with the timestamp as you can see, in my js file I use this code:
const addPerson = (person) => {
const myPerson= new Model(person);
return myPerson.save();
};
Then when I add a new vaccine for the same person this does not get the timestamp, I'm using this code for that:
const addPersonVaccine = async ({ params, body }) => {
if (!params) return Promise.reject("Invalid ID");
const vaccines = [body];
const foundPerson = await Model.updateOne(
{
_id: params,
},
{
$push: {
vaccines: vaccines,
},
}
);
return foundPerson;
};
This is what my body inside vaccines array has:
[ { vaccine: '5f72c909594ee82d107bf870', dose: 'Primera' } ]
The problem is that I have no results about the next timestamps, as you can see in my mongo atlas document:
1:Object
dose:Array
_id:5f893a9ca98e97188c93fea8
vaccine:5f70259796f7ed21e85be523
2:Object
dose:Array
_id:5f893acda98e97188c93fea9
vaccine:5f7023ad96f7ed21e85be521
Is that the best way to implement timestamps in subdocuments or sub schemas?
I will appreciate your answers, thnks 👏
You can use mongoose schema timestamps options to the inner schemas
const mongoose = require("mongoose");
const forumSchema = new mongoose.Schema(
{
title: { type: String, required: true },
biddings: [
{
type: new mongoose.Schema(
{
biddingId: String,
biddingPoints: Number
},
{ timestamps: true }
)
}
]
},
{ timestamps: true }
);
const Forum = mongoose.model("Forum", forumSchema);
module.exports = Forum;
for more Mongoose schema set timestamp on nested document
I want to create a database thats defined by user, each user can have his own flavor of database. So i used strict: false But Now the problem is I cant make the user define the type of each schema under the model
Example
const mongoose = require('mongoose');
const testSchema = new mongoose.Schema({
label: {
required: 'please enter label',
trim: true,
type: String
},
url: {
type: String,
trim: true,
},
settings: {} //User defined
}, {
timestamps: true, strict: false
});
module.exports = mongoose.model('test', testSchema);
In the above case, I want the setting to be defined by user
like,
{
"label": "About Us",
"url": "www.google.com",
"settings": {
"name": {
"type": "String", //Problem is Here, i can't send datatype directly
"required": true
},
"age": {
"type": "Number",
"required": true,
"enum": [10, 12]
}
}
}
So please tell help me, how can i make the user define the type of the schema?
strict: true does not mean You can pass anything to settings field.
It means Your schema format is dynamic - You can have unexpected field names in document that is not defined in schema.
Answer to Your issue:
Seems like You want subdocument, let's make another schema and attach it as type:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const types = Schema.Types;
const testSettingsSchema = new Schema({
name: {
type: types.String,
required: true
},
age: {
type: types.Number,
required: true
enum: [10, 12]
}
},
{
_id : false,
timestamps: false,
strict: false
});
const testSchema = new Schema({
label: {
required: 'please enter label',
trim: true,
type: types.String
},
url: {
type: types.String,
trim: true,
},
settings: {
type: testSettingsSchema,
required: true
}
},
{
timestamps: true,
strict: true
});
module.exports = mongoose.model('test', testSchema);
But to gain more flexibility and avoid creating big test document (since user may push unpredictable big object), create another schema: testSettings that points to test_settings collection and make settings field to be reference:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const types = Schema.Types;
const testSettingsSchema = new Schema({
name: {
type: types.Mixed
},
age: {
type: types.Mixed
}
},
{
collection: 'test_settings',
timestamps: false,
strict: false // tells to mongoose that schema may "grow"
});
mongoose.model('testSettings', testSettingsSchema);
const testSchema = new Schema({
label: {
required: 'please enter label',
trim: true,
type: types.String
},
url: {
type: types.String,
trim: true,
},
settings: {
type: types.ObjectId,
ref: 'testSettings'
default: null
}
},
{
collection: 'tests',
timestamps: true,
strict: true
});
module.exports = mongoose.model('test', testSchema);
create it as:
const Test = mongoose.model('test');
const TestSettings = mongoose.model('testSettings');
app.post('/tests', async (req, res) => {
try {
const testSettings = await TestSettings.create(req.body.settings);
const test = new Test(req.body);
test.settings = testSettings._id;
await test.save();
res.status(201).send({_id: test._id});
}
catch(error) {
res.status(500).send({message: error.message});
}
});
and on request time get it as:
const Test = mongoose.model('test');
app.get('/tests/:id', async (req, res) => {
try {
const test = await Test.findById(req.params.id)
.populate('settings')
.lean();
res.status(200).send(test);
}
catch(error) {
res.status(500).send({message: error.message});
}
});
define your setting field as Schema.Types.Mixed , so you can able to set any kind of fields inside it , like Number , String , Array , Date , Boolean ..etc
const mongoose = require('mongoose');
const testSchema = new mongoose.Schema({
label: {
required: 'please enter label',
trim: true,
type: String
},
url: {
type: String,
trim: true,
},
settings: {
type:Schema.Types.Mixed ,
default: {}
}
}, {
timestamps: true, strict: false
});
module.exports = mongoose.model('test', testSchema);
While saving the document :
app.post('/save',function(req,res){
var setting = {};
setting.age= req.body.age;
setting.name= req.body.name;
var test = new Test({
test.label: req.body.label;
test.url :req.body.url;
test.setting: setting
});
test.save(function(err){
if(err) {return res.json(err);}
else{ res.json({status:'success',message:'saved.'});}
});
});
Just in case someone is having this issue with NestJS and schemaFactory, that's how I solved it:
...
#Schema({ strict: false })
export class Content {}
#Schema()
export class Deadletter extends Document {
#Prop({type: Header})
header: Header;,
#Prop({type: Metadata})
_metadata?: Metadata;
#Prop({type: Content})
content: any;
}
export const deadLetterFullSchema = SchemaFactory.createForClass(Deadletter);