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}
]
}
],
})
Related
Consider there is documents like this in DB:
{
name: 'my list',
items: [
{ type: 'book', id: 5364 },
{ type: 'car', id: 354 },
{ type: 'laptop', id: 228 }
]
}
I need to grab data of each item from its own collection, based on type value.
I searched about it but couldn't figure out the correct approach.
Expected output:
{
name: 'my list',
items: [
{ type: 'book', id: 5364, data: [{...}] },
{ type: 'car', id: 354, data: [{...}] },
{ type: 'laptop', id: 228, data: [{...}] }
]
}
Mongoose schema of first collection (above):
{
name: String,
items: {
type: Array,
default: []
}
}
And other collections that must be looked up has corresponding _id field.
There are a few different ways to do this. To be most similar to what you have currently, you just need to make one small change. type is a keyword in Mongoose, so if you want to have a field in your schema which is actually called "type", you need to use the word twice. Once for the Mongoose keyword and again to define your schema.
{
name: String,
items: [{
type: { type: String},
id: Number,
data: []
}]
}
If the data field is coming from another collection, you could use a reference and then call populate() on your find method.
// ItemSchema
{
name: String,
items: [{
type: { type: String },
id: Number,
data: [{
type: Schema.Types.ObjectId,
ref: 'OtherSchemaName'
}]
}]
}
// Find Method with populate
const myList = await ItemSchema.find({type: "book"}).populate('data')
// Result
{
name: 'my list',
items: [
{ type: 'book', id: 5364, data: [{...}] },
]
}
I wanted to populate 'item' in here and I'm getting the below error. It is an object array. This method worked for a normal array but gave an error for an object array. How to resolve it?
// Get the reserved list
const reservedDetails = await reserveInventory
.findOne({ memberID: id })
.select("itemsList")
.populate({
path: "item",
model: inventoryItem,
});
Error:
Cannot populate path `item` because it is not in your schema. Set the `strictPopulate` option to false to override.
reserveInventory Model:
const reserveInventorySchema = mongoose.Schema({
memberID: {
type: String,
ref: "member",
required: true,
},
itemsList: [
{
item: {
type: String,
ref: "inventoryItem",
},
quantity: {
type: Number,
},
},
],
});
module.exports = mongoose.model("reserveInventory", reserveInventorySchema);
inventoryItem Model:
const inventoryItemSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
available: {
type: Number,
required: true,
},
},
{
timestamps: true,
}
);
module.exports = mongoose.model("inventoryItem", inventoryItemSchema);
you got it wrong here
// Get the reserved list
const reservedDetails = await reserveInventory
.findOne({ memberID: id })
.select("itemsList")
.populate({ path: "itemsList.item"});
I have the following schema:
const UserSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
trackedProducts: [
{
type: Schema.Types.ObjectID,
ref: 'product',
},
],
});
And I am trying to write a migration for existing data to:
const UserSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
trackedProducts: [
{
productDetails: {
type: Schema.Types.ObjectID,
ref: 'product',
},
otherStuff: {}
},
],
});
So effectively I want to map each value of the trackedProducts array to a more complex object in the new schema. For example, I want:
[objref1, objref2, objref3]
To be migrated to:
[
{ productDetails: objref1, otherStuff: {} },
{ productDetails: objref2, otherStuff: {}},
{ productDetails: objref3, otherStuff: {}}
]
I have been trying to play with $set along the lines of
await db.collection('users').update({}, { $set { <no idea> }});
but am unsure of a) how to go value by value of the array and b) how to get a reference to the original value.
You can use update with aggregation pipeline, starting from MongoDB 4.2,
use updateMany() instead of update()
$map to iterate loop of trackedProducts and return with object { productDetails }
await db.collection('users').updateMany({},
[{
$set: {
trackedProducts: {
$map: {
input: "$trackedProducts",
in: { productDetails: "$$this" }
}
}
}
}]
)
First of all, I'm pretty new to MongoDB, Mongoose and Express. I'm trying to create a Mongoose model that has two arrays that I want to populate with multiple objects called itemSchema but I'm not sure how I'm supposed to update the array short of using findOneAndUpdate but since my array is initially empty there is no initial ID until a document is created. With the method that I have defined below - any already existing data in the food array is replaced by a new array. Below is my model -
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
drinks: [
{
id: String,
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
food: [
{
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
]
});
module.exports = Item = mongoose.model("item", itemSchema);
I don't know if I'm defining the schema correctly. I know that it isn't very DRY ( since both arrays contain the same types ) but since I believe this is such a simple use case I don't want to define two separate schema for Drink and Food when I could just create one Schema.
router.post("/food", async (req, res) => {
try {
// Create an object from the request that includes the name, price and description
const newItem = {
name: req.body.name,
price: req.body.price,
description: req.body.description
};
// pass the object to the Items model
let item = new Items(newItem);
// add to the comments array
console.log("the new comment ", newItem);
item.food.unshift(newItem);
item.save();
// return the new item array to confirm adding the new item is working.
res.json(item);
} catch (error) {
// Display an error if there is one.
res.send(404).json(error);
}
});
The issue with the approach above comes from how I'm supposed to update the array. I defined the function below to update the food array for example but a new array gets created every single time. I believe that is has to do with not having Id param that I can use to provide the model with the findOneAndUpdate method. Any help would be greatly appreciated. Thank you in advance.
As per my opinion you can make your schema more simple as in your food and drinks array all the fields are same so you can simply take one more field as itemType and then you do not need to take two separate sub docs for food and drinks.
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
itemType: { type: String }, // FOOD or DRINK
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
});
If you wants to know more about updating in array with findOneAndUpdate() then i will explain two simple task to perform with this function.
CASE:1 If array of your sub doc is empty then you can push new document in your sub doc as below:
var updatedData = await Model.findOneAndUpdate(
{
_id: doc._id
},
{
$push: {
drinks: {
name: drink.name,
price: drink.price,
description: drink.description,
}
},
},{ new: true }
).lean().exec();
CASE:2 If you want to update existing sub doc by sub doc id then you can update as below:
var updatedData = await Model.findOneAndUpdate(
{
'drink._id': drinkId
},
{
$set: {
'drink.$.name': drink.name,
'drink.$.price': drink.price,
'drink.$.description': drink.description,
},
},{ new: true }
).lean().exec();
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]
});