Mongoose, proper way to update documents with deep nested schemas - mongodb

I would like to know what would be the most efficient way to update a document that also has a nested schema in it. Normally I would just use Model.findByIdAndUpdate(id, updatedValues) ..., however if I try to do that with a document that has a deep nested schema I get this error: CastError: Cast to Embedded failed for value "{ _id: 5b1936aab50e727c83687797, en_US: 'Meat', lt_LT: 'Mesa' }" at path "title".
My Schemas look something like:
const titleSchema = new Schema({
en_US: {
type: String,
required: true
},
lt_LT: {
type: String,
default: null
}
})
const categorySchema = new Schema({
title: titleSchema
});
const ingredientSchema = new Schema({
title: {
type: titleSchema,
required: true
},
category: categorySchema,
calories: {
type: Number,
min: 0,
default: 0
},
vitamins: [String]
})
And I try to update like so:
{
title: {
en_US: 'Pork',
lt_LT: 'Kiauliena'
},
category: {
_id: '5b193a230af63a7e80b6acd8',
title: {
_id: '5b193a230af63a7e80b6acd7'
en_US: 'Meat',
lt_LT: 'Mesa'
}
}
}
Note, I get the new category object from a separate collection using just the category _id, but the final update object that goes into the findByIdAndUpdate() function looks like the one above.
The only workout I found is to remove the category from the updated values, update the document via findByIdAndUpdate(), get the updated document, assign the new category to it and save it.
It works just fine and all, but that requires two calls to the database, is it possible to do it in just one?

Try updating your schema like this:
const titleSchema = new Schema({
en_US: {
type: String,
required: true
},
lt_LT: {
type: String,
default: null
}
});
const ingredientSchema = new Schema({
title: {
type: titleSchema,
required: true
},
category: {
title: titleSchema
},
calories: {
type: Number,
min: 0,
default: 0
},
vitamins: [String]
});

Related

How to construct a custom validator that makes a MongoDB field required if another field is a particular value?

I am using MongoDB and Mongoose.
Suppose I have the following Schema.
const notificationMetaSchema = new mongoose.Schema({
listingId: { type: mongoose.Schema.Types.ObjectId },
});
const notificationSchema = new mongoose.Schema({
category: { type: String, required: true, enum: [ "categoryA", "categoryB" ] },
meta: notificationMetaSchema,
});
I want my "listingId" field to be required only when the "category" field is "categoryA".
This validation ideally exists during both document creation and updates.
How do I construct a custom validator to achieve this effect?
EDIT
I have tried the following:
const notificationSchema = new mongoose.Schema({
category: { type: String, required: true, enum: [ "categoryA", "categoryB" ] },
meta: {
listingId: {
type: mongoose.Schema.Types.ObjectId,
required: function () {
return [
"categoryA",
].includes(this.category);
}
},
},
});
However, when I call the following query:
Notification.findOneAndUpdate({}, $set: { category: "categoryA", meta: {} }).exec();
No validation error is thrown
You can write a javaScript function for a field in mongoose schema, that function can act as custom validator, Your schema should look like :
const notificationSchema = new mongoose.Schema({
category: {
type: String,
required: true,
enum: ["categoryA", "categoryB"]
},
meta: {
listingId: {
type: mongoose.Schema.Types.ObjectId,
required: function checkRequiredOrNot() {
/** This function returns true or false, 'this.category' will retrieve current object's 'category' value */
return this.category == "categoryA" ? true : false;
}
}
}
});

Push item to array, only first property is included. Mongoose

Schema:
let projectSchema = new Schema({
filters: [
{
name: { type: String, required: true},
items: {
q: { type: Number, required: true}
}
}
],
});
Update function:
const project = await mongoose.model('project').findById(id).exec();
console.log(filter); // { name: 'abc', items: [ { q: 3}]
project.filters.push(filter);
console.log(project.filters); // { _id: "123", name: 'abc' } // items array is missing
await project.save();
When I fetch a document via mongoose, then add an item to an array of that doc, only the first property is included.
Why is that?
I prefer not to use $push since the benefits of mongoose (validation etc) is not respected when $push is used.
The items field is an object instead of an array. Change your schema:
let projectSchema = new Schema({
filters: [
{
name: { type: String, required: true},
items: [ // square brackets here
q: { type: Number, required: true}
]
}
],
})

mongodb doesnt save 2 objects inside the main object

I want to save an object that has and object and array inside it. But when I end up saving the data in the mongo, it doesnt save a few properties.
like "entityMap": {}, data: {}
body =
{ entityMap: {},
blocks:
[ { key: '637gr',
text: 'Books selected for the you ',
type: 'header-four',
depth: 0,
inlineStyleRanges: [Array],
entityRanges: [],
data: {} } ] }
Heres how my mongo schema structured.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const bookSchema = new Schema({
body: {
type: {},
required: false
},
templateName: {
type: String,
required: true
},
subject: {
type: String,
required: true
},
googleId: {
type: String,
required:true
},
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
mongoose.model('books', bookSchema);
When declaring the property with type {}, mongoose uses the Schema.Types.Mixed type. This way the property may contain anything, but mongoose won't detect changes made to it. You have to manually tell mongoose that the property was modified:
book.body = { foo: { bar: { quux: 'foo' } } }
book.markModified('body');
book.save()
Mongoose SchemaTypes

How do you change a fieldName via mongoose method + change it in schema?

I have a schema like this:
const StorySchema = new Schema({
authorId: String,
title: String,
longUrl: String,
shortUrl: String,
comment: String,
category: String,
author: String,
date: { type: Date, default: Date.now, index: true },
votes: { type: Number, default: 1, index: true },
});
I want to change the votes field on the schema to be called votesCount and at the same time I want to actually change the schema.
Would I just do these in the same file?
const StorySchema = new Schema({
authorId: String,
title: String,
longUrl: String,
shortUrl: String,
comment: String,
category: String,
author: String,
date: { type: Date, default: Date.now, index: true },
votesCount: { type: Number, default: 1, index: true },
});
const StoryModel = mongoose.model('story', StorySchema);
StoryModel.update({}, { $rename: { votes: 'votesCount' } }, { multi: true, strict: false }, function(err, blocks) { });
Or do I not do this at all in the code? I have never dealt with database schema changes, so I'm not sure how / where to apply schema changes.
Make your changes in the Schema and the controller, as whatever name you use in your Schema field should also tally with the one in your Controller.
Eg.
const StorySchema = new Schema({
authorId: String,
title: String,
longUrl: String,
shortUrl: String,
comment: String,
category: String,
author: String,
date: { type: Date, default: Date.now, index: true },
votesCount: { type: Number, default: 1, index: true },
});
In your controller
let form = {
authorId:req.body.*****,
title:req.body.*****,
longUrl:req.body.*****,
shortUrl:req.body.*****,
comment:req.body.*****,
category:req.body.*****,
author:req.body.*****,
date:req.body.*****,
votesCount:req.body.*****
};
Note: the main point am trying to make here is that, the very name you used in the Schema should also the the same name you're gonna use for your controller.
I hope this is answers your question.
best use updateMany as
db.students.updateMany( {}, { $rename: { "nmae": "name" } } )
and change in your controller or directly replace previous field name to new field name where ever possible.
My Suggestion is better u first replace in controller or your project and if your project running in production update your new controller than u replace the field name using $rename

Meteor Simple-Schema Collection2 help needed for build nested schema

I am trying to build a collection schema with Meteor Collection2.
Possible schema for my collection is :
{
items: {
id: Meteor.ObjectID
title: String,
Desc: String,
createdAt: new Date(),
creator: String,
invitedUsers: [String],
items2: [{
id: String,
pos: Number,
dur: Number,
startTime: Number,
endTime: Number,
duration: Number
items3: [{
id: String,
itemtype: String,
style: String,
items4: [{
id: String,
position: Number,
dome: String
}]
}]
}]
}
}
So how can I best build the Collection2 collection with the above nested schema and the best way to perform insert, update and remove queries on it.
Update:
So now as suggested by Andrei Karpushonak this is what I have got:
Item4 = new SimpleSchema({
position: {
type: Number
},
dome: {
type: String
}
});
Item3 = new SimpleSchema({
itemtype: {
type: String
},
style: {
type: String
},
items4: {
type: [Item4]
}
});
Item2 = new SimpleSchema({
pos: {
type: Number
},
dur: {
type: Number
},
startTime: {
type: Number
},
endTime: {
type: Number
},
duration: {
type: Number
},
items3 : {
type: [Item3]
}
});
Items = new Meteor.Collection2('items', {
schema : new SimpleSchema({
title: {
type: String
},
Desc: {
type: String
},
createdAt: {
type: new Date()
},
creator: {
type: String
},
invitedUsers: {
type: [String]
},
items2: {
type: [Item2]
}
})
});
So now I am trying to figure out how can I do the insert, update, remove operations on such a schema?
Do I do for individual schemas for as whole? An example will be very helpful.
Any help will be highly appreciated.
Thanks in Advance,
Praney
You have two options:
Create sub-schema:
item2 = new SimpleSchema({
id: String,
pos: Number
})
item1 = new SimpleSchema({
id: Meteor.ObjectID,
title: String,
items2: [item2]
});
Use dot notation:
item1 = new SimpleSchema({
id: String,
pos: Number,
"item2.id": String,
"item2.pos": String
});
I think first approach fits your model better, as you have array of objects as value of items2