Problem with adding an item to an array of mongoose reference objects - arrays

In my NodeJS and MongoDB app, I have 2 mongoose schemas:
companySchema:
const companySchema = new Schema({
name: {
type: String,
required: true
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product',
required: false
}]
});
companySchema.statics.addProduct = function (productId) {
let updatedProducts = [...this.products];
updatedProducts.push(productId);
this.products = updatedProducts;
return this.save();
}
module.exports = mongoose.model(‘Company’, companySchema);
productSchema:
const productSchema = new Schema({
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
}
});
module.exports = mongoose.model('Product', productSchema);
Every time I add a new product to productSchema, I would like to add the _id of the newly created product to products array in companySchema in order to easily access products later on.
To accomplish this, I wrote:
const Company = require('../models/company');
const Product = require('../models/product ');
exports.postAddProduct = (req, res, next) => {
const name = req.body.name;
const quantity = req.body.quantity;
const product = new Product({
name: name,
quantity: quantity
});
product.save()
.then(product => {
return Company.addProduct(product._id);
})
.then(result => {
res.redirect('/');
})
.catch(err => console.log(err));
}
I am getting an error: TypeError: this.products is not iterable.

You are setting a static method, which is a method on the model rather than the document instance.
Therefore, this refers to the model itself, not the individual document.
Unlike the document, the model doesn’t have an array (iterable) called products so it can’t be spread into a new array.
Try using methods instead of statics:
companySchema.methods.addProduct = function (productId) {
...
}
I hope this helps.

Related

How to use mongoose findById() and populate() in javascript?

Im trying to create a list of items for each users, which means user can create items and ı want to keep that data for each user.
Which I create two different schemas for that in:
user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
userSchema = new Schema({
unique_id: Number,
email: String,
username: String,
password: String,
passwordConf: String,
teamname: String,
items: [{ type: Schema.Types.ObjectId, ref: 'Item' }],
});
(ItemSchema = new Schema({
createrId: { type: Schema.Types.ObjectId, ref: 'User' },
productname: String,
description: String,
price: String,
totalStock: String,
})),
(Item = mongoose.model('Item', ItemSchema));
User = mongoose.model('User', userSchema);
module.exports = { Item, User };
After creating 2 schemas ı used them in index.js that routers everything together.
index.js
router.get('/create_item', function (req, res, next) {
return res.render('items.ejs');
});
router.post(
'/create_item',
function (
req,
res,
next // Try to create items in database
) {
console.log(req.body);
var itemInfo = req.body;
if (
!itemInfo.itemname ||
!itemInfo.itemdsc ||
!itemInfo.itemstock ||
!itemInfo.itemprice
) {
res.send();
} else {
var NewItem = new Item({
createrId: loggedInUser._id,
productname: itemInfo.itemname,
description: itemInfo.itemdsc,
price: itemInfo.itemprice,
totalStock: itemInfo.itemstock,
});
NewItem.save(function (err, Item) {
if (err) console.log(err);
else console.log('Item added succesfully !');
});
}
User.findById(loggedInUser._id)
.populate('items')
.exec(function (err, result) {
if (err) {
console.log(err);
} else {
console.log('Suceess ? : ', result.items);
console.log(loggedInUser._id);
}
});
}
);
I keep the data for loggedInUser in '/login' so it is return me a value.
My output is
"Suceess ? :[]
6379921faf39150008fa1b88
Item added succesfully !
Which ı think the problem is from the output it trying to populate item after Item is created but how can ı solve this problem ?
Thanks
The problem is that your code is not executing sequentially (NewItem.save logs after User.findById).
Try to use async await, also you will need to add the newly created item _id to the corresponding User to add the new reference to the items array:
router.post(
'/create_item',
async function (
req,
res,
next // Try to create items in database
) {
console.log(req.body);
try {
var itemInfo = req.body;
if (
!itemInfo.itemname ||
!itemInfo.itemdsc ||
!itemInfo.itemstock ||
!itemInfo.itemprice
) {
return res.send('Incomplete parameters');
}
const newItem = await Item.create({
createrId: loggedInUser._id,
productname: itemInfo.itemname,
description: itemInfo.itemdsc,
price: itemInfo.itemprice,
totalStock: itemInfo.itemstock,
});
const result = await User.findByIdAndUpdate(loggedInUser._id, {
$push: { items: newItem._id },
}, { new: true })
.populate('items')
.exec();
console.log('Suceess ? : ', result.items);
console.log(loggedInUser._id);
return res.send('Finished');
} catch (e) {
console.log(e);
}
}
);

Pushing data to an array in already existing object with axios

i have a object which looks like this:
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
}
to an characters array i want to add the id of characters.
I do it by form and then i handle submit like this:
const handleSubmit = (values) => {
console.log("dodano aktora do filmu!");
console.log(values);
addActorToMovie(values);
history.goBack();
};
the addActorToMovie action:
export const addActorToMovie = (resp) => ({
type: types.ADD_CHAR_TO_MOVIE,
payload: resp,
});
and the reducer:
case types.ADD_CHAR_TO_MOVIE:
console.log(action.payload);
return {
...state,
...state.episodes.map(function (item) {
return item.id === action.payload.episodeId
? {
id: item.id,
title: item.title,
release_date: item.release_date,
series: item.series,
img: item.img,
characters: [...item.characters, action.payload.actor],
}
: { ...item };
}),
};
It all works, but the problem is that i dont want to do it loccaly. Im using an database with json-server, and I want to do an Axios Request so that it would add a data to the database.
And i don't know how to do this, when i use axios.post it adds an object to my episodes array, if im using axios.put it changes an object. Is there any possibility to push the data to an array as i do it with the code above, but with axios so that it would be added to database?
My approach looked like this:
export const addActorToMovieAxios = (value) => {
console.log(value);
return async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:3000/episodes/`,
value
);
console.log(response);
dispatch(addActorToMovie(response.data));
} catch (ex) {
console.log(ex);
}
};
};
but as I said this does add a new object to an array.....
"episodes": [
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
},
{
"episodeId": 1,
"actor": "1",
"id": 2
}
]
So just to be clear I understand your question, you have an object that already exists in your DB, and you want to push something onto the 'characters' array in that existing object, without creating a new object, correct?
To do this, I would use Mongo for your DB and define two Mongoose Schemas, one for the existing object (let's call it TVShow) and one for the Characters within that object. Your two Schemas will look like this:
TVShowModel.js:
const mongoose = require('mongoose');
const CharacterModel = require('./CharacterModel')
const TVShowScheme = new mongoose.Schema({
title: {
type: String,
},
release_date: {
type: Date,
},
series: {
type: String,
},
img: {
type: String,
},
characters:[
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
},
],
examQuestions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'CharacterModel'
}
]
})
module.exports = mongoose.model('TVShowModel', TVShowScheme )
CharacterModel.js:
const mongoose = require('mongoose');
const CharacterModel= new mongoose.Schema({
characterName: {
type: String,
},
actorName: {
type: String,
},
}) // add any other fields you want here
module.exports = mongoose.model('CharacterModel', CharactModelScheme )
Then, create your Axios post request. Make sure you send when you send the 'value' variable to your server, it contains the id (or perhaps the unique title) of the object you'll be 'pushing' to. Push won't work in axios/react, so we'll use the 'spread' opperator instead.
Your router will look like this:
const CharacterModel= require ('../models/CharacterModel');
const TVShowModel= require ('../models/TVShowModel');
const router = express.Router();
router.post('/episodes', async function(req,res){
try{
const tvshow = await TVShowModel.find({title: req.body.title})
// edit as needed
console.log("FOUND TV Show: "+tvshow )
const characterName= req.body.characterName
const actorName = req.body.actorName
const newCharacter = new CharacterModel({
characterName,
actorName,
})
console.log("new character created: "+newCharacter)
tvshow[0].CharacterModel = [...tvshow[0].CharacterModel,newCharacter];
await tvshow[0].save()
.then(()=>res.json('New Character Added to DB'))
.catch(err=>res.status(400).json('Error: ' + err))
} catch(e){
console.log(e)
}
})
Hope this was clear!

Next JS / mongoose: Assign ObjectId's into array before saving new object

I'm trying to create a new object (Group) containing selected objectId's (User). I break my head since days now but I don't find the right way to do it.
The group model looks like this:
import mongoose from 'mongoose';
const groupSchema = new mongoose.Schema(
{
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
title: { type: String },
members: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
],
},
{
timestamps: true,
}
);
const Group =
mongoose.models.Group || mongoose.model('Group', groupSchema);
export default Group;
and here is the user model:
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema(
{
name: { type: String },
},
{
timestamps: true,
}
);
const User =
mongoose.models.User || mongoose.model('User', userSchema);
export default User;
So I want users give the possibility to create groups, select existing users from a list and add them into members array.
I understand how to create a group and assign users after by findAndUpdate and query them with populate, but I realy don't get it how to add users ObjectId's into array before creating the group. Would someone give me an advice how to do the post operation for this case?
Here is my current post operation, which of course doesn't work.
import nc from 'next-connect';
import db from '../../../utils/db';
const handler = nc();
//create group
handler.post(async (req, res) => {
await db.connect();
const user = await User.findById(req.params.id);
const newGroup = new Group({
creator: req.user._id,
title: req.body.title,
members: [{
item: user,
}],
});
const group = await newGroup.save();
await db.disconnect();
res.send({ message: 'Group created', group });
});
export default handler;
Thanks for helping me out!!
Your models are looking good and as you defined the
members: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
],
you can just save the objectIds as an array and at the time of query you can assign the populate method which will automatically populate users according to the objectId.
So in order to save the group members try updating your save method like this
handler.post(async (req, res) => {
await db.connect();
// you can remove this query
// const user = await User.findById(req.params.id);
const newGroup = new Group({
creator: req.user._id,
title: req.body.title,
// array of objectIds (user ids)
members: [req.params.id],
});
const group = await newGroup.save();
await db.disconnect();
res.send({ message: 'Group created', group });
});
And in order to get the group members you can add the populate method at the time of querying groups
Group.find({}).populate('members') // the ref field name in Group model

Mongoose/Mongo: Update Not Saving

I'm extremely perplexed by this issue that I'm having with mongo/mongoose. I'm essentially trying to get an array of products, delete a certain product from the array, and then update the shopping chart with the new array that omits the selected product. Here's the snippet of code I'm dealing with:
const remove = (req, res, next) => {
console.log('here is the product id ' + req.body.cart.product)
delete req.body._owner // disallow owner reassignment.
Cart.find({_id: req.user.cartId})
.then((products1) => {
console.log("array of products: " + products1[0].product)
const index = products1[0].product.indexOf(req.body.cart.product)
console.log("index valeu: " + index)
if (index > -1) {
products1[0].product.splice(index, 1)
return products1[0].product
}
return products1[0].product
})
.then((products2) => {
console.log('Second Promise Input: ' + products2)
Cart.update({_id: req.user.cartId}, {$set: {product: products2}})
})
.then(() => res.sendStatus(204))
.catch(next)
}
And here's the output from my server:
Server listening on port 4741
here is the product id 5952b57ea52d092b8d34c6b0
array of products: 5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0
index valeu: 0
Second Promise Input: 5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0,5952b57ea52d092b8d34c6b0
PATCH /carts-decrease/595b037e128cfd37e0c864d7 204 38.773 ms
According to my console.logs, I'm getting the array just the way I want it but it simply does not update the shopping cart with the new array. I've been staring at this code for far too long and I'd appreciate a second set of eyes on this. Thanks.
P.S. Ignore the fact that the product ids are all the same, its just a testing variable
Cart Schema:
'use strict'
const mongoose = require('mongoose')
const cartSchema = new mongoose.Schema({
product: {
type: Array,
required: false
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: false
}
}, {
timestamps: true,
toJSON: {
virtuals: true,
transform: function (doc, ret, options) {
const userId = (options.user && options.user._id) || false
ret.editable = userId && userId.equals(doc._owner)
return ret
}
}
})
const Cart = mongoose.model('Cart', cartSchema)
module.exports = Cart
Product Schema:
'use strict'
const mongoose = require('mongoose')
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
description: {
type: String,
required: true
}
}, {
toJSON: {
virtuals: true
}
})
const Product = mongoose.model('Product', productSchema)
module.exports = Product
Show request:
const show = (req, res) => {
const product = {}
product.array = []
// console.log(req.cart.product)
const promises = []
Promise.all(req.cart.product.map(function (id) {
return Product.find({_id: ObjectId(id)})
})).then(function (products) {
console.log(products)
req.cart.product = products
return res.json({
cart: req.cart.toJSON({virtuals: true, user: req.user})
})
}).catch(function (err) {
console.log(err)
return res.sendStatus(500)
})
}
I would recommend you to slightly modify your cartSchema and store products in the form of an array of embedded documents:
const cartSchema = new mongoose.Schema({
products: [{
name: { type: String },
price: { type: Number }
...
}]
...
});
If you do this you can simply use the $pull update operator to remove products from your cart:
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
In your case the query should then look like this:
Cart.update(
{ _id: req.user.cartId },
{ $pull: { products: { '_id': req.body.cart.product } }}
);
As the embedded documents will have their own ObjectId there will only be one document matching the query.

How do I find the recursive path to an object reference in mongodb/mongoose?

I have a mongoose model with parent references:
const GroupSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
parent: {
type: Schema.Types.ObjectId,
ref: 'group'
}});
I would like to get the path to an object by traveling up the parent references and adding them to an array.
I've tried doing this a few ways, but I'm stuck here:
const groupId = req.params.id;
const path = [];
function addToPath(id){
Group.findById(id)
.then(results => {
if(results.parent !== null){
path.unshift({ _id: results._id, name: results.name });
addToPath(results.parent);
} else {
return res.send(path);
}
})
.catch(next);
}
addToPath(groupId);
I can't seem to get results into the array. What is the best way to do this? Am I moving in the right direction?

Resources