I have a server setup in node.js that uses mongoose. The problem i am having is that when i update a database as follows
Questions.update({id:questionID}, {$push: {answers: {answer:answer,answeredBy:username}}}, callback);
I see that the data has been added in MongoCompass but when i query it using
Questions.find({tags:interests},callback);
the updated data is not returned. But if i restart the server and again query, i receive the updated result.
And if i just save the data and query by using .save method and query, the database is updated.
So the problem has to be with my .update method right? I really cant find what i am doing wrong
my database is as follows
let mongoose =new require('mongoose');
const uuidv4 = require('uuid/v4');
//Define a schema
let Schema = mongoose.Schema;
let QuestionsSchema = new Schema({
id: {
type: String,
unique: true
},
title: String,
details: String,
askedDate: {type: Date, default: Date.now},
tags: [{
type: String
}],
askedBy: String,
answers: [{
answeredBy: String,
answer: String,
answeredDate: {type: Date, default: Date.now}
}]
});
let Questions = module.exports = mongoose.model('Questions', QuestionsSchema);
module.exports.addQuestion = function (newQuestion, callback) {
//generating a new random id for each question
newQuestion.id = uuidv4();
newQuestion.save(callback);
};
module.exports.addAnswer = function (username,answer, questionID, callback) {
Questions.update({id:questionID}, {$push: {answers: {answer:answer,answeredBy:username}}}, callback);
};
module.exports.getQuestions = function (interests, callback) {
Questions.find({tags:interests},callback);
};
The methods are called as follows
Questions.getQuestions(user.tags[i], function (err, questions) {
if (err) {
let output = {
error: {
status: "fail",
name: err.name,
message: err.message,
text: err.toString()
}
};
let statusCode = err.status || 500;
res.json(output);
res.send();
}
else {
questions.map((data) => {
if (!sentTags.includes(data.id)) {
jsonResponse.push(data);
sentTags.push(data.id);
}
});
if (i === user.tags.length - 1) {
res.send(JSON.parse(JSON.stringify(jsonResponse)));
}
}
});
and
Questions.addAnswer(user.username,req.body.answer, req.body.id, function (err) {
if (err) {
let output = {
error: {
status: "fail",
name: err.name,
message: err.message,
text: err.toString()
}
};
let statusCode = err.status || 500;
res.status(statusCode).json(output);
res.send();
}
else {
res.json({status: "success", message: "Answer Added"});
res.send();
}
});
Since MongoDB is eventually consistent, if the Question is saved and then immediately updated, it could be the case that the changes haven't been fully propagated yet.
In Mongoose, try specifying the read concern to 'primary' with this:
Questions.find({tags:interests}).read('p').exec(callback)
How to specify read concerns in Mongoose
MongoDB Read Concern documentation
Depending on the use-case, you may also want to explore the write concerns:
MongoDB Write Concerns
Related
This is my schema:
const productSchema = new mongoose.Schema({
name: String,
imageUrl: String,
category: String,
price: Number,
description: String,
featured: {
default: false
},
rating: [
{
userName: String,
score: Number,
comment: String
}
]
});
And this is how I was trying to push data into my database.
app.route("/review").post(function(req, res) {
const score = req.body.score;
const comment = req.body.comment;
if (req.isAuthenticated()) {
const review = {
$push: {
rating: {
userName: req.user.fName,
score: score,
comment: comment
}
}
};
console.log(review, req.body.productName);
Product.updateOne({ name: req.body.productName }, review, function(
err,
done
) {
if (err) {
console.log(err);
} else {
res.redirect("/products");
}
});
} else {
res.redirect("/login");
}
});
In the official documentation, it says that this is the way to push data in the MongoDB array. But still having no luck.
Help me to push reviews in the rating array field.
Thank You.
for my answer im using mongoose function findOneAndUpdate you can use updateOne instead , The writing to the database will be in a async function in order to not block the node process (will be alot faster).
Puting all the code in a try-catch block will allow you to control any errors if accrued.
You can read more about mongoose driver function and findOneAndUpdate here:
https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
more about async function:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
app.route('/review').post(async function(req, res) {
//destructuring values from body-parser
const { score, comment, productName } = req.body;
if (req.isAuthenticated()) {
//creating the rating obj
const rating = {
userName: req.user.fName,
score: score,
comment: comment
};
console.log(review, productName);
//setting it all in a try-catch block
try {
const product = await Product.findOneAndUpdate(
{name: productName},
{ $set: { rating: rating } },
{ new: true }
).then(err => {
if (err) throw err;
return res.redirect('/products');
});
res.redirect('/login');
} catch (error) {
console.log(error);
}
}
});
Hopefully this makes sense, good luck!
I try to call a related list of logs for a certain user via Mongoose populate. Who can help me with finishing the response?
These are the schemes:
const logSchema = new Schema({
logTitle: String,
createdOn:
{ type: Date, 'default': Date.now },
postedBy: {
type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
const userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
logs: { type: mongoose.Schema.Types.ObjectId, ref: 'logs' }
});
mongoose.model('User', userSchema);
mongoose.model('logs', logSchema);
Inspired by the Mongoose documentary (see above) and other questions in relation to this subject I think I got pretty far in making a nice get. request for this user. I miss the expierence to 'translate it' to Express.
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
response = { //question
log: {
user: user.logs
}
};
res
.status(200)
.json(response);
});
} else { }); //
}
};
The response in Postman etc would be something like this:
{
"log": {5a57b2e6f633ce1148350e29: logTitle1,
6a57b2e6f633ce1148350e32: newsPaper44,
51757b2e6f633ce1148350e29: logTitle3
}
First off, logs will not be a list of logs; it will be an object. If you want multiple logs for each user, you will need to store is as an array: logs: [{ type: mongoose.Schema.Types.ObjectId, ref: 'logs' }]
From the Mongoose docs: "Populated paths are no longer set to their original _id , their value is replaced with the mongoose document returned from the database by performing a separate query before returning the results." In other words, in your query user.logs will be the logs document for each user. It will contain all the properties, in your case logTitle, createdOn, and postedBy.
Sending user.logs as json from the server is as easy as: res.json(user.logs). So your query can look like this:
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
res.status(200).json(user.logs)
});
} else { }); //
}
};
I hope this makes it a little bit clearer!
I'm using validate in mongoose schema for unique name now face issue while someone updates that record It won't allow the user to update because already have an entry in a database.
I've schema & code like following.
Schema
let mongoose = require('mongoose'),
Schema = mongoose.Schema;
let pTSchema = mongoose.Schema({
type: {
type: String,
required: true,
validate: {
validator: function(v, cb) {
v = v.toLowerCase();
PT.aggregate([ // Query for validate type should be unique if already exist than through error.
{
$addFields:{
lowerCase: { $toLower: "$type" }
}
},
{
$match:{
lowerCase: v
}
}
], function(err,docs){
console.log(docs, v);
cb(docs.length == 0);
});
},
message: 'p already exists!'
}
}
});
module.exports = PT = mongoose.model('pt', pTSchema);
Insert New Record.
// Working as expected with validation
var newPT = new PT();
newPT.type = req.body.type;
newPT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: newPT});
});
Update Records.
// While update single record it also executes schema validate and won't give permission to save.
// Please give me some suggestion on this.
PT.findOne({ _id : where }, function(err, responsePT) {
if (!PT) {
return res.status(500).send({ status: false, message: "ERROR" });
}
responsePT.type = req.body.type;
responsePT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: responsePT});
});
});
At, Final I didn't get any solution so, I update my code with .pre('save', It's fixed my problem.
I ignore updated entry.
So I've been working on a project and I finished most of it, but then this error popped up, saying there is something that is undefined, here is the error:
E11000 duplicate key error index: build-a-voting-app.polls.$votedIp_1 dup key: { : undefined }
Here is my code for my create new mongo schema file (polls.model.js)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const pollSchema = new Schema({
title: { type: String, unique: true, required: true },
choices: [
{
title: { type: String, required: true },
count: { type: Number, default: 0 }
}
],
votedIp: [{ type: String, unique: true }],
createdAt: {type:Date, default:Date.now()},
createdBy: String
});
const Poll = mongoose.model('polls', pollSchema);
module.exports = Poll;
Here is the function where I add the inputs
function submitVote(field, res, ip) {
Poll.findOneAndUpdate(
{ choices: { $elemMatch: { title: field } } },
{ $inc: { 'choices.$.count': 1 }, $addToSet: { 'votedIp': ip } },
{ new: true },
function (err, poll) {
if (err) throw err;
res.json({ updated: poll });
}
);
}
Here is how I first created it
var newPoll = new Poll({
title: req.body.title,
choices: choicesArr,
createdBy: req.session.user.username || req.session.user
}).save(function (err, poll) {
if (err) throw err
res.redirect('/mypolls')
});
If you want to see the full code please go to https://github.com/ElisaLuo/Freecodecamp-Build-A-Voting-App
I'm using the ip addresses for checking if the user has voted or not (I'm building a voting app), but right now, I cannot even create a new schema / poll. Does anyone know why the error happens and how I can solve it?
#Elisa l - you may want to read this - mongoose enforce unique attribute on subdocument property
However, I did manage to test with mongoose-mock and the behavior is as expected - test results below (please do check the two versions of votedIp in the test code snippets)
and as described in the MongoDb document referenced in the above link. Mongoose does not enforce the unique integrity, MongoDb does.
With the mocha test below (inserted as snippets, not to run the code but just for better readability, please ignore the messy look of the comments in the code but the permutation and combination had to be worked out!), I did manage to create the mongoose schema by adding a create method in "Poll". please note the change in the schema - votedIp: { type: String, unique: true }, you can change it to array in the test code.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var pollSchema = new Schema({
title: { type: String, unique: true, required: true },
choices: [
{
title: { type: String, required: true },
count: { type: Number, default: 0 }
}
],
votedIp: { type: String, unique: true },
createdAt: {type:Date, default:Date.now()},
createdBy: String
});
// Below code added by RJv (ie. me :)
var NewPoll = new mongoose.Schema(pollSchema);
NewPoll.statics.create = function (params, callback) {
var newUpdate = new NewPoll(params);
newUpdate.save(function(err, result) {
callback(err, result);
});
return newUpdate;
};
var Poll = mongoose.model('Model', NewPoll);
module.exports = Poll;
var expect = require('chai').expect,mongooseMock = require('mongoose-mock'),proxyquire=require('proxyquire'),
sinon = require('sinon'), chai=require('chai'),sinonChai = require("sinon-chai");chai.use(sinonChai);
var Poll;
before(function(done){
Poll = proxyquire('./Poll', {'mongoose': mongooseMock});
done();
})
describe('model', function() {
/* beforeEach(function (done) {
Poll = proxyquire('./Poll', {'mongoose': mongooseMock});
done();
});*/
it("should be called once",function(done){
setTimeout(done, 15000);
var callback = sinon.spy();
var poll1 = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
"votedIp":"9.1.2.1","createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
// Below to pass data for votedIp as an array as described in the original schema by Elisa
//"votedIp":[{"add1":"9.","add2":"1.","add3":"2.","add4":"1"}],"createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
//expect(poll1.votedIp[0].add1+poll1.votedIp[0].add2+poll1.votedIp[0].add3+poll1.votedIp[0].add4).equals("9.1.2.1");
expect(poll1.save).calledOnce;
console.log(JSON.stringify(poll1));
expect(poll1.votedIp).equals("9.1.2.1");
done();
});
it('should expect same ip to get added', function(done) {
this.timeout(5000);
setTimeout(done, 15000);
var callback = sinon.spy();//mock(new Poll({ title: 'jv', choices: [{title:"jv#gmail.com","count":"1"}], votedIp:[{ad1:"9.",add2:"1.",add3:"2.",add4:"1"}],createdAt:"25/07/2017",createdBy:"Lisa"}));
var poll = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
"votedIp":"9.1.2.1","createdAt":"23/07/2017","createdBy":"Lisa"}, callback);
// var poll = Poll.create({ "title": 'jv', "choices": [{"title":"jv#gmail.com","count":"1"}],
// Below to pass data for votedIp as an array as described in the original schema by Elisa
// "votedIp":[{"add1":"9.","add2":"1.","add3":"2.","add4":"1"}],"createdAt":"25/07/2017","createdBy":"Lisa"}, callback);
// expect(poll.votedIp[0].add1+poll.votedIp[0].add2+poll.votedIp[0].add3+poll.votedIp[0].add4).equals("9.1.2.1");
expect(poll.save).calledOnce;
expect(poll.votedIp).equals("9.1.2.1");
//assert(spy.calledOnce);
done();
});
});
Are you calling submitVote multiple times in quick succession? You might be running into https://jira.mongodb.org/browse/SERVER-14322.
The suggested fix for this is to check the error and if one of the calls fails retry it.
https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes
In the below user schema there is a foobar.events field, that I am trying to push new hashes (that are received from an API POST request) to.
var userSchema = mongoose.Schema({
foobar: {
id : String,
token : String,
email : String,
name : String,
events : [{
action : String,
timestamp : Date,
user_xid : String,
type : {type: String},
event_xid : String
}]
}
});
And here is the logic for that Express route:
app.post('/foobar/post', function(req, res) {
var jb_user_xid = req.body['events'][0]['user_xid'];
var jb_timestamp = req.body['events'][0]['timestamp'];
var jb_action = req.body['events'][0]['action'];
var jb_type = req.body['events'][0]['type'];
var jb_event_xid = req.body['events'][0]['event_xid'];
User.findOne({'foobar.id':jb_user_xid}, function(err, user) {
console.log(user);
user.foobar.events.push({
user_xid: jb_user_xid,
timestamp: jb_timestamp,
action: jb_action,
type: jb_type,
event_xid: jb_event_xid
});
user.save(function(err) {
if (err){
console.log("Error on save: " + err);
}
else {
console.log("Save successful");
}
});
});
res.writeHead(200);
res.end();
return;
});
The find method is executed successfully, but the following error is thrown when trying to save to the database: Error on save: TypeError: Object.keys called on non-object - any idea why this error is being thrown?
This thread had a similar problem, but changing the findOne to findById broke my user query.
As a side note, this is what is returned in req.body from the API:
{ events:
[ { action: 'updation',
timestamp: 1408846680,
user_xid: 'aguxwNqb_Xg87buMyP6Wiw',
type: 'move',
event_xid: 'vhAkgg1XwQvLynAkkCc8Iw' } ],
notification_timestamp: 1408846680 }
And here is what's returned from the User.findOne method
{ __v: 17,
_id: 53f7d23e432de20200970c10,
foobar:
{ id: 'aguxwNqb_Xg87buMyP6Wiw',
name: 'Test User',
token: 'W3AjaI7_iOWilcKRpmxenQWi',
events: [] }
}
This error was actually due to old data within my Mongo database. The events field was full of extra strings. I deleted these and my original code began working successfully. No changes to the above code were necessary.
I tried your code and it works perfectly.. for me.. try to check mongoose module version or something.if u have still problem please do it using update function rather than save..It would be more performance oriented.
this is the following i used
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback () {
// yay!
});
var userSchema = mongoose.Schema({
foobar: {
id : String,
name : String,
events : [{
action : String,
timestamp : Date,
user_xid : String,
type : {type: String},
event_xid : String
}]
}
});
var User = mongoose.model('user', userSchema);
/*
//used for saving.
var person = new User({ foobar:
{ id: 'aguxwNqb_Xg87buMyP6Wiw',
name: 'Test User',
token: 'W3AjaI7_iOWilcKRpmxenQWi',
events: [] }
});
person.save(function(err,data){
console.log(err);
console.log(data);
})
*/
User.findOne({'foobar.id':'aguxwNqb_Xg87buMyP6Wiw'}, function(err, user) {
console.log(user);
user.foobar.events.push({ action: 'updation',
timestamp : 1408846680,
user_xid: 'aguxwNqb_Xg87buMyP6Wiw',
type: 'move',
event_xid: 'vhAkgg1XwQvLynAkkCc8Iw' });
user.save(function(err) {
if (err){
console.log("Error on save: " + err);
}
else {
console.log("Save successful");
}
});
});