I am having below schema in my API which gets details of username and the products he added to the cart.
const mongoose = require('mongoose');
mongoose.connect('mongodb connection').then(()=>{
console.log('DB connection is successfull');});
const AutoIncrement = require('mongoose-sequence')(mongoose);
const cartSchema = new mongoose.Schema({
cartId : {
type : Number
},
username : {
type : String
},
productsInCart : [{
productId : {type : Number,required : true},
productName : {type:String},
quantity : {type:Number}
}],
statusOfCart : {
type : String,
default : 'Open'
}},{ timestamps: true });
cartSchema.plugin(AutoIncrement,{id : 'cart_seq',inc_field : 'cartId'});
let cartModel = mongoose.model('carts',cartSchema);
module.exports = cartModel;
As you can see in the above code I am also using the mongoose-sequence to make cartId as a auto-incremented field.
Now, I have a POST request which gets the below JSON in request body and adds it to the cart collection in the MongoDB using the create method.
{
"username":"admin",
"productsInCart":
[
{
"productId":1,
"productName":"Watch",
"quantity":4
},
{
"productId":2,
"productName":"Phone",
"quantity":5
}
]
}
The code inside the Route Handler for the POST request in Express API would look something like this
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
let insCartData = await cartModel.create(ctMod,{new:true});
if(insCartData.length > 0)
{
return res.status(200).json({
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
The above code inserts two entries into the collection like below instead of one
{
"statusOfCart": "Open",
"productsInCart": [],
"createdAt": "2021-01-04T15:25:35.188Z",
"updatedAt": "2021-01-04T15:25:35.188Z",
"cartId": 13,
"__v": 0
},
{
"statusOfCart": "Open",
"productsInCart": [
{
"_id": "5ff332a891aa170b60a21ea9",
"productId": 1,
"productName": "Watch",
"quantity": 4
},
{
"_id": "5ff332a891aa170b60a21eaa",
"productId": 2,
"productName": "Phone",
"quantity": 5
}
],
"username": "admin",
"createdAt": "2021-01-04T15:25:35.210Z",
"updatedAt": "2021-01-04T15:25:35.210Z",
"cartId": 14,
"__v": 0
}
can you help me understand why there is duplicate entries in my db?
I'm not sure why you are using cartModel.create(ctMod,{new:true}); inorder to create a new entry to your collection.
You can simply do like this :
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
try{
let insCartData = await cartModel.save();
return res.status(200).json({
error:false,
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
catch(err){
console.error("error inserting data", err);
return res.status(500).json({error:true, message:"Something went wrong :("}).
}
UPDATE :
Reason for the duplicate entry is that you are passing the {new:true} flag in the create API. It's used to return the updated document in the findOneAndUpdate method. But in create/save, the mongoose return the data by default. Hence the flag is not needed.If you omit the flag, duplicate entries can be prevented.
Also, as you are using the create method to save the new entry, you need not create an instance of the cartModel. ie.,
let ctMod = new cartModel();
can be just
let ctMod = {}
Hope it helps!
Related
I try to update a MongoDB collection with Mongoose & NodeJS but I have the following error who appears sometimes, but not all the times :
MongoServerError: Plan executor error during findAndModify :: caused by :: The positional operator did not find the match needed from the query.
Here is my code:
NodeJS
try {
this.myArrayUpdate = { 'description': 'test' };
this.update = { $set:
{ 'myArray.$': this.myArrayUpdate }
};
const cardId = ObjectId("61d6e320520fac65775a7ba8")
const userId = ObjectId("61a8e433648e1963ae7358be");
const filter = {
userId: userId,
'myArray._id': cardId,
};
const options = {
upsert: true,
new: true
};
MyCollection
.findOneAndUpdate(
filter,
this.update,
options,
)
.then ( () => {
return res.status(204).json({ message: 'MyCollection updated ' });
});
} catch (error) {
throw error;
}
Mongodb
{
"_id" : ObjectId("61a8e433648e1963ae7358be"),
"myArray" : [
{
"description" : "test",
"starting" : null,
"ending" : null,
"_id" : ObjectId("61d6e320520fac65775a7ba8")
},
{
"description" : "test 2",
"starting" : null,
"ending" : null,
"_id" : ObjectId("61d6e320520fac657812991a")
},
],
}
If found a part of an answer, each time I update my subdocument array, the _id change so it's why there is an error, if I update my page no more error !
Is there a possibility to not create a new _id when updating a subdocument array ?
Thanks for your answer,
David
The document you provided doesn't have an userId field, so your filter won't match the document. The field is called _id:
const filter = {
_id: userId,
'myArray._id': cardId,
};
Also, the way the update document is written, the entire matched element in myArray will be overwritten, but I assume you only want to update description. It would look like this:
this.update = { $set:
{ 'myArray.$.description': 'test' }
};
This is an example of updating a document in an array. Here's a working example in Mongo playground.
Document in mongodb collection 'users' is
{
"$oid": "5e612272bcb362513824ff9b",
"name": "abcd",
"email": "test#test.com",
"cart": {
"items": [{
"productId": {
"$oid": "5e614367cae25319c4388288"
},
"quantity": {
"$numberInt": "1"
}
}]
}
}
For a particular users._id and a productId in cart.items, I need to increase the quantity by 1
My nodejs code is
IncrCartItem(prodId){
const db=getDb()
return db
.collection('users').
updateOne({_id: new mongodb.ObjectId(this._id),'this.cart.items.productId': new mongodb.ObjectId(prodId)},{$inc : {'this.cart.items.quantity':1}})
}
Is the query right for checking multiple conditions in updateOne()??
You're kinda there, to "and" them all together you just keep appending them, however the field is wrong and you need to use the positional operator ($) - https://docs.mongodb.com/manual/reference/operator/update/positional/
const filter = {
_id: new mongodb.ObjectId(this._id),
'cart.items.productId': new mongodb.ObjectId(prodId)
};
const update = {
$inc : { 'this.cart.items.$.quantity': 1 }
};
IncrCartItem(prodId){
const db=getDb()
return db
.collection('users').
updateOne(filter,update)
}
Hello I have relational _ids in an array. The object content is in other collection. I can only view Object("_id") in my array.
I want to edit object of those _ids. How can I edit by using relational _ids ??
I have data response like this
{
"admins": {
"users": [
"5d089739d8aae228d7f10c1e" //<------ edit this id object
],
"email": "jertacomlu#desoz.com",
"password": "$2a$10$C/DsmoHK57vIUt7g4wRsYulHLigmc1wGLObwu2/qkFkSeYp28bAOy",
},
"_id": "5d089264d8aae228d7f10c1d",
"companyName":"Company 1",
"__v": 0
}
So this id is stored in other collection name Userr.
the object is like this:-
[ {
"_id": "5d089739d8aae228d7f10c1e",
"email": "jertacomlu#desoz.com",
"password": "$2a$10$0Slx3yFeb7UD0qep.TWSI.JpQGl0CzlGhNJ162JtA5Uvt6osyrIl.",
"firstName": "Sima",
"lastName": "Cezo",
"phoneNumber": "8877123456",
"__v": 0
}
]
I want to edit these fields. How can I do by using relation _id ?
EDIT:-
my parent controller
var admin = new Admin();
admin.companyName = req.body.companyName;
admin.admins = {
email : req.body.email,
password: req.body.password,
role : "admin",
verified :"false",
active: "OFF",
users : []
};
admin.find({id:"5d089264d8aae228d7f10c1d"}).populate("users").then(data=>{
//update the fields which you would find in the data object
data.save()
//your data would be changed.
})
You must create a method at model User , example to edit email :
User.edit=async function (id,newEmail){
let user = await User.findOne({_id:id}).exec();
user.email=newEmail;
await user.save();
}
In Admin ,
...
let admin = await Admin.findOne({_id:id}).exec();
await User.edit(admin.users[0])
...
I have an expressjs router that looks in MongoDB collection using the mongoose findById method. It returns an object where inside there exist an userHasSelected array with users id. I dont want to return users id, but just check if current users (the one who made the request) exist in the array. If he is then return true instead of returning the user id.
The verifytoken middleware in the router adds a user id property to the request.That user id is available in the get router message - can i somehow pass that to the Mongoose schema ???
//My router
router.get('/challenge/:challengeId', verifyToken ,function (req, res){
//+ Return one challenge and its options
//- Check if userId is set to options and winner properties
let userId = req.userId;
console.log(userId);
let challengeId = req.params.challengeId;
Challenge.findById(challengeId, (err, suc)=>{
if(err){
res.status(304).send(err);
}
Challenge.
res.status(200).send(suc);
});
})
// And the mongoose Schema
const mongoose = require('mongoose');
var Schema = mongoose.Schema;
//Optionsschema is use in the ChallengeSchema
var OptionSchema = new Schema({
name: { type: String},
isCorrect : { type: Boolean },
description: { type: String },
image : { type : String },
userHasSelected : { type : Object, get : returnUserChallengeStatus}
})
OptionSchema.set('toObject', { getters: true });
OptionSchema.set('toJSON', { getters: true });
var ChallengeSchema = new Schema({
shortEventId : String,
organization: String,
name: String,
winner: String,
options : [OptionSchema]
});
ChallengeSchema.set('toObject', { getters: true });
ChallengeSchema.set('toJSON', { getters: true });
ChallengeSchema.virtual('born').get(function(value) {
return this.name + "plus andet"
});
module.exports = mongoose.model('challenge', ChallengeSchema);
So again - I dont want to return the user id from the userHasSelected array - just check if he is there and if yes, use a getter or a method to set value to true.
Updated explanation
The findById returns this object / document
{
"_id":"5b86dc5bfb6fc03893e55001",
"shortEventId": "d2d3",
"organization": "Braedstrup",
"name": "1. december",
"winner": "5b864cbaa9ce291b148ddd6d",
"options": [
{
"name": "Matas",
"isCorrect": "true",
"description": "Matas er byens førende indenfor pleje og Matas er byens førende indenfor pleje og omsorg Matas er byens førende indenfor pleje og omsorg",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png",
"userHasSelected": [
{
"userId": "5b864cbaa9ce291b148ddd6d"
}
]
},
{
"name": "Føtex",
"isCorrect": "false",
"description": "Føtex er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Føtex.png"
},
{
"name": "Kvickly",
"isCorrect": "false",
"description": "Kvickly er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
},
{
"name": "MC Jørgensen",
"isCorrect": "false",
"description": "MC Jørgensen er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
}
],
"startDate": "2018-10-06T00:00:00.000Z",
"endDate": "2018-10-06T23:59:00.000Z"
}
So the nested array 'userHasSelected' contains information about the users id. I do not want to send that - instead I would like to a {userId : true}.
I have read that getters a able to handle outgoing data.
Posible Solution
I could make the check inside the router get method before returning the object to the client like this
// If user is in array set user to true. I would like to move this responsibility to the schema / document.
suc.options.forEach(option => {
if(Array.isArray(option.userHasSelected))
option.userHasSelected = {userId : true}
});
But I would really like schema to be responsible for that - Is that possible?
I had similar issue and found a workaround. Simply create an optional field on your responsible modal schema, let call it "status". On your controller, check if your array includes requested user's id and write to that field. For example;
on your schema;
caseStatus: {
type: Boolean,
required: false
},
voters: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}],
then on your controller;
let theCase = await Case.find({speciality: res.locals.user_speciality }).exec();
let status = theCase.voters.includes(res.locals.user_id);
caseItem.caseStatus = status;
I have a record like this
{
"_id" : ObjectId("57025c35e31f7274c1195c26"),
"token" : "0ffed58b-57ed-4a2a-bb09-97c64f0f2bd2",
"key" : "silly key",
"user" : ObjectId("55e3f8fcc78dc516096dc3e2"),
"twlprofile" : "Test Blog"
}
It is the only one in the collection.
I am trying to update and need the update to upsert if it can't find anything, so I have this code
var id = "55e3f8fcc78dc516096dc3e2";
var tokendoc = {
"token": "05c50aa0-5a8c-4fad-b27f-db64a0355b4f",
"key": "silly key",
"user": "55e3f8fcc78dc516096dc3e2",
"twlprofile": "Test Blog"
}
var tokenrecord = tokensCollection.update(
{ where: { user: id } },
tokendoc,
{ upsert: true },
function(err,tokenres){
//this part not important right now
});
which if I run it ends up inserting another document with the same user property. I have also tried using BSON.ObjectID with the id variable and it doesn't help.
How do I get this to work like a real upsert?