Mongoose query problem - $push pushing element twice - node.js

I want to push single string into array from document in my mongodb collection.
I'm using nodejs with mongoose v5.3.1
I'm putting data into my html form and then, submitting it to server.
There is my HTML:
<form action="/addchat" method="post">
<input type="text" class="form-control" name="chatid" id="addchatid">
<input type="text" class="form-control" name="chatname">
<select class="form-control form-control-sm" name="chatbot" id="chatbot">
<option value="684206793:AAH5uDpus4Ngw1Z60pj6iOedBGCM8Vq0">botname1</option></select><br>
<button type="submit" class="btn btn-primary">Add</button>
</form>
There is my app code:
app.post('/addchat', async (req, res) => {
var czat = {
name: req.body.chatname,
chatid: req.body.chatid
}
await botsmodel.updateOne({
token: req.body.chatbot
}, {
$push: {
chats: czat
}
},
async (err, result) => {
if (err) {
console.log(`Error updating chats ${err}`)
} else {
console.log(`Chats updated for ${req.body.chatbot}`);
}
});
await res.redirect('/')
});
There is my collection schema:
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
},
toObject: {
virtuals: true
}
};
var botyschema = new mongoose.Schema({
_id: String,
name: String,
token: String,
chats: Array
}, schemaOptions);
After executing, my console looks like it have pushed "czat" object once:
Chats updated for 684206793:AAH5uDpus4Ngw1Z60pj6iOedBGCM8Vq0
But in my collection two objects have appended into my array, it looks that:
"chats": [
{
"name": "chat_main",
"chatid": "100516120633"
},
{
"name": "chat_main",
"chatid": "100516120633"
}
],
I'm missing something in my schema or query?

I had similar problem, and I solved it by changing $push to $addToSet.
$addToSet documentation
Hope it'll be helpful.

I am not sure if addToSet is the best solution because the query being executed twice.
If you used a callback and a promise simultaneously, it would make the query executes twice.
So choosing one of them would make it works fine.

https://mongoosejs.com/docs/schematypes.html
Try changing your schema to this
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
},
toObject: {
virtuals: true
}
};
var botyschema = new mongoose.Schema({
_id: String,
name: String,
token: String,
chats: [{
"name": String,
"chatid": Number
}]
}, schemaOptions);
Or you could try just
chats: [{}]
But I guess that opens it up to recieve any input.

I have problem same you, so I tried remove "await", and I successfully

Related

Expected 'property' to be of type string, instead found type object - Dynamoose

I am working with AWS DynamoDB and Dynamoose trying to fetch records using Scan function, but facing an issue that is not recognizable for me.
Stragenly, it's able to fetch records from another table in the same way and successfully get the records.
Here's my Code:
const vehicleMasterSchema = new dynamoose.Schema({
"id": String,
"customer_account_number": String,
"fuel_type": String,
"make": String,
"model": String,
"odometer_gatex": String,
"plate_no": String,
"rfid_gatex": String,
"sales_agreement_id": String,
"vehicle_category": String,
"vehicle_id": String,
}, {
"timestamps": {
"createdAt": "create_date",
"updatedAt": null // updatedAt will not be stored as part of the timestamp
}
});
const vehicleMasterModel = dynamoose.model("vehicle_master", vehicleMasterSchema, { "create": false });
router.post('/getFuelingStatus', (req, res) => {
var companyInfo = req.body;
try {
console.log(typeof vehicleMasterModel);
vehicleMasterModel.scan("customer_account_number").eq(companyInfo.customerId).exec((error, results) => {
if (error) {
console.error(error);
} else {
res.json(results);
}
});
} catch (error) {
res.json(error);
}
});
The TypeMismatch error is coming up only for this model same code is working for the other table.
Console Error
My Table
This appears to be related to this github issue on Dyanmoose
My guess is that the problem could be related with the name of your attribute, model.
In fact, this is the actual case: the following code, extracted from the source code in Document.ts is the one which is overwriting your model property:
Object.defineProperty(this, "model", {
"configurable": false,
"value": model
});
This is how the Document looks like before:
And after the execution of the aforementioned code:
This code is executed when processing the Scan exec function in DocumentRetriever.ts when the library maps every Item returned by DynamoDB to their internal Document representation, exactly in this line of code:
const array: any = (await Promise.all(result.Items.map(async (item) => await new this.internalSettings.model.Document(item, {"type": "fromDynamo"}).conformToSchema({"customTypesDynamo": true, "checkExpiredItem": true, "saveUnknown": true, "modifiers": ["get"], "type": "fromDynamo"})))).filter((a) => Boolean(a));
The error you reported is a consequence of that change when the type of the returned Item is checked against your schema model in the checkTypeFunction:
const {isValidType, matchedTypeDetails, typeDetailsArray} = utils.dynamoose.getValueTypeCheckResult(schema, value, genericKey, settings, {"standardKey": true, typeIndexOptionMap});
if (!isValidType) {
throw new Error.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${typeof value}.`);
...
Please, try a different name, I think it will work properly.
Schema must be like this :
const ImageGalleryFoldersSchema = new Schema({
key: {
type: String,
hashKey: true,
required: true,
},
displayName: {
type: String,
required: true,
},
parentFolderKey: {
type: String,
required: false,
},
isActive: {
type: Boolean,
default: true,
required: false,
},
}, {
timestamps: true,
});
Maybe your problem is caused due to asynchronous behaviour.
To be more specific, I think that by the time you call the "scan"-function-chain the body-request has not been finished. However, due to the nature of Hoisting, the object "companyInfo" was already being initialised before you enter the function-call.
Therefore, you may get the specified "TypeMismatch"-error.
Could you please try implementing the following async/await-structure and tell me if this helps:
router.post('/getFuelingStatus', async (req, res) => {
var companyInfo = await req.body;
try {
console.log(typeof vehicleMasterModel);
vehicleMasterModel.scan("customer_account_number").eq(companyInfo.customerId).exec((error, results) => {
if (error) {
console.error(error);
} else {
res.json(results);
}
});
} catch (error) {
res.json(error);
}
});

mongodb findOneAndUpdate replace document instead of updating specified field [duplicate]

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Mongoose overwrite the document rather that `$set` fields

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

How do i $set and $push in one update MongoDB?

I'm trying to $push and $set at the same time, $push is working just fine, when it comes to $set, it generates this error:
MongoError: The positional operator did not find the match needed from
the query. Unexpanded update: files.$.name
Here's the code
Course.update(
{
_id: req.body.courseId,
'files.fileUrl': { $ne: url }
},{
$push: { files: { fileUrl: url } },
$set: {'files.$.name': file.name},
}, function(err, count) {
if (err) return next(err);
console.log("Successfully saved")
});
and the ORM model, I'm using mongoose
var CourseSchema = new Schema({
files: [{
fileUrl: String,
name: { type: String, default: 'File name'}
}]
});
Any help would be appreciated. Thanks.
As the error states looks like the query used is returning no documents or returning documents having no files[].
Another reason for which it might be throwing error is that you're trying to $push & $set in the same field files and probably running into an issue similar to https://jira.mongodb.org/browse/SERVER-1050
IMHO, there is no good reason to use the same field in $push & $set, instead you can simply change
$push: { files: { fileUrl: url } },
$set: {'files.$.name': file.name},
to
$push: { files: { fileUrl: url, name: file.name } },
I have written similar kind of query for my project
Hope u could relative this to your scenario
exports.candidateRating = function(req, res) {
console.log(req.query);
console.log(req.body.RoundWiseRatings);
Profiles.update({
"name": req.query.name
}, {
$set: {
"ratings": req.body.ratings,
},
$push: {
"RoundWiseRatings": req.body.RoundWiseRatings
}
}, {
multi: true
}, function(error, profiles) {
if (error) {
}
return Profiles.find({
name: req.query.name
}, function(err, profiless) {
console.log(profiless);
if (err) {
return handleError(res, err);
}
return res.status(200).json(fnStruncturedData(profiless[0].RoundWiseRatings));
});
});};
And this worked for me :)

Save/design nested form to save/update data in mongodb via mongoose according to mongoose schema

I have created the following mongoose schema for saving a quiz in mongodb -
var answerSchema = new Schema({
answer: String,
correct: Boolean
});
var questionSchema = new Schema({
title: String,
tag: String,
chapterName: String,
marks: Number,
negativeMarks: Number,
correctAnswerExplanation: String,
hint: String,
answers: [answerSchema]
});
var quizSchema = new Schema({
courseId: Number,
courseName: String,
lectureId: Number,
lectureName: String,
popupTime: Number,
teacherName: String,
questions: [questionSchema]
});
var Quiz = mongoose.model('quiz', quizSchema );
I am able to store the hard coded values in the following way -
var quiz1 = new Quiz({
courseId: 4,
lectureId: 5008,
popupTime: 3,
teacherName: 'Charles Xavier',
questions: [
{
"title": "Which is the Capital of India " ,
"answers": [
{ "answer": "Delhi", "correct": true },
{ "answer": "Bangalore", "correct": false },
{ "answer": "Mumbai", "correct": false },
{ "answer": "Chennai", "correct": false }
]
},
{
"title": "Where is facebook hosted? " ,
"answers": [
{ "answer": "Heroku", "correct": true },
{ "answer": "Digital Ocean", "correct": false },
{ "answer": "AWS", "correct": true },
{ "answer": "SWF", "correct": false }
]
}
]
});
quiz1.save(function(err, q) {
console.log("Saved ----");
console.log(q);
res.json(q);
});
This works perfectly fine.
Now which is the best way to create a GUI form so that when the user fills in the details, it is converted to json and it is saved in mongodb via mongoose.
I have come up with the following form -
<form id="aj">
<input type="text" name="questions[0]" value="" placeholder="Question Title">
<input type="text" name="answers[0]" value="" placeholder="Answer 1">
<input type="text" name="answers[1]" value="" placeholder="Answer 2">
<input type="text" name="answers[2]" value="" placeholder="Answer 3">
<input type="text" name="answers[3]" value="" placeholder="Answer 4">
<button>Send</button>
</form>
And on form submit I serialize the form data using this snipped -
var o = {};
var a = $( "#aj" ).serializeArray();
$.each(a, function() {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
And in the express route handler I am saving it in the similar way -
This is what I get in the request body via express -
{ questions: [ 'Q 1' ],
answers: [ ' A 1', 'A 2', 'A 3', 'A 4' ] }
var quiz = req.body(i.e the above object)
var add = new Quiz(quiz);
add.save(function(err, quiz) {
if(err) {
console.log(err);
} else {
console.log("Saved");
console.log(quiz);
}
});
But I am getting the following error -
[TypeError: Cannot use 'in' operator to search for '_id' in Q 1]
How do I solve this, please help as I just started with mongodb and mongoose and I come from a SQL background.
Also, how should I design a form that takes inputs of multiple questions and each question can have multiple answers and each answer has a bit to indicate whether it is correct answer or not.

Resources