I have this module I'm working on, its a post form that takes data for an expense (name, date, amount, an image, and description). I've figured out how to get the post data into mongo for ONE row of data, but I want a user to be able to submit multiple rows (i.e multiple expenses). Unfortunately, when I try to submit the form with multiple instances of this data, I get an error!
ERROR: Expense validation failed: date: Cast to Date failed for value "[ '2018-07-01', '2018-07-09' ]" at path "date"
Any thoughts on how to correctly submit multiple rows of the aforementioned data?
INFO:
I'm using express for the server
MongoDB for database
ExpenseForm.js --> (using pug template engine)
button#submitExpense(type='submit' form='addExpenseForm').btn Submit
// ADD EXPENSE FORM
form#addExpenseForm(method='POST' action='/expenses').row
div.col-md-12.add-expense-row-wrapper
div#expenseRow.row.form-group
input.form-control.col-md-2(type='text' name='name' placeholder='Enter Name*')
input.form-control.col-md-2(type='date' name='date')
input.form-control.col-md-2(type='number' name='amount' placeholder='Enter Amount*')
input.form-control.col-md-2(type='text' name='description' placeholder='Description*')
input.col-md-3(type='file' name='file' id='files' placeholder='Upload receipt' multiple)
button#deleteExpenseRow.col-md-1.btn.delete-expense-row(type='button' )
i.fas.fa-trash-alt
div.row.add-expense-button-wrapper
button#addExpenseRow.btn(type='button')
i.far.fa-plus-square
Also forgot my Schema
expense.js -->
const ExpenseSchema = new mongoose.Schema({
employee: {
type: String,
required: true,
trim: true
},
date: {
type: Date,
required: true,
default: Date.now
},
amount: {
type: Number,
required: true,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
description: {
type: String,
required: true
},
file: {
type: Buffer,
required: true
}
});
var Expense = mongoose.model('Expense', ExpenseSchema);
module.exports = Expense;
AND THE POST ROUTE
index.js -->
// POST /expenses
router.post('/expenses', function(req, res, next) {
// Expense.getExpenses({
// name: req.body.name,
// date: req.body.date,
// amount: req.body.amount,
// description: req.body.description,
// file: req.body.file
// }, 10, function(err, post) {
// if (err) {
// return next(err);
// }
// console.log(req.body);
// res.json(post);
// });
// create object with form input
var expenseData = {
employee: req.body.name,
date: req.body.date,
amount: req.body.amount,
description: req.body.description,
file: req.body.file
};
// store data from from into mongo
Expense.create(expenseData, function(error) {
if (error) {
return next (error);
} else {
console.log(expenseData);
return res.redirect('/dashboard');
}
});
});
So, data will come as array. name will contain array of all names and so on. You can iterate over all and save in database and then return after saving last item.
var employee = req.body.name;
var date = req.body.date;
var amount = req.body.amount;
var description = req.body.description;
var file = req.body.file;
var expenseData = {};
employee.forEach(function(element, index, array) {
expenseData = {
employee: employee[index],
date: date[index],
amount: amount[index],
description: description[index],
file: file[index]
};
Expense.create(expenseData, function(error) {
if (error) {
return next(error);
} else {
if (index === employee.length - 1) {
return res.redirect('/dashboard');
}
}
});
});
The steps are as follows:
You need indeed to collect your data in an array - something that will look like this: expenseArr = [{row1Data}, {row2Data}, ...].
Inside your post route you need to import your expenses model - something that will look like this: const expenses = require('thePathAndNameOfYourSchemaExportFile')
Call expenses.insertMany(expenseArr) method inside your post route and mongoose will do what's necessary. In this last step, don't forget that you need to wait for the insertMany to complete (recommended that you have a callback in case something goes wrong).
Related
I have a function on my server that is supposed to get a post by its ID. The function works up until the "foundPost" constant, where I can't seem to find one of the documents from the "posts" array. I've tried substituting findOne for find and the ObjectIds work for the const 'post'.
I've double checked that post_id is 62067c1211eea1531d5872f4
Here is the function to find a post:
const postById = async (req, res) => {
const userId = req.params.userId;
const post_id = req.params.post_id;
const posts = await Post.findOne({ user: userId });
console.log(posts); //see this below
const foundPost = await posts.findOne({ "upload": post_id }); //error here
console.log(foundPost);
return res.json({ success: true, Post: foundPost });
};
Here is what 'console.log(posts)' returns:
[
{
upload: new ObjectId("623b681bdf85df9086417723"),
edited: false,
title: 'Test 1',
description: 'testing post 1',
name: 'John ',
sharedPost: 0,
},
{
upload: new ObjectId("62067c1211eea1531d5872f4"),
edited: false,
title: 'Test 2',
description: 'testing post 2',
name: 'John ',
sharedPost: 0,
}
]
I'm hoping that the function will return:
{
success: true,
{
upload: new ObjectId("62067c1211eea1531d5872f4"),
edited: false,
title: 'Test 2',
description: 'testing post 2',
name: 'John ',
sharedPost: 0,
},
}
Can anyone see why the line const foundPost = await posts.findOne({ "upload": post_id }); isn't working?
Thank you for your help.
****** Response to answer ******
Hello, thanks a lot for your answer, unfortunately it's still giving an error. Please see below the model for the code I'm using:
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
post: [
{
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
upload: {
type: Schema.Types.ObjectId,
ref: "upload",
},
title: {
type: String,
},
description: {
type: String,
},
},
],
date: {
type: Date,
default: Date.now,
},
});
I noticed that you used 'userProfile.posts' which I adapted to 'userProfile.post' to match this schema. I'm also not sure if you wanted to use 'subdoc' or 'subDoc' in line 11 of your code, but I tried both with the same error for each. I determined that the code stuck at the const subDocs = userPosts.filter(filter); line. I've looked into the .filter method you've used and can't find any potential errors. Not sure what the issue is.
Thanks
The issue is that you cannot run another mongo query on objects that were result of a previous query.
//will return a single document if found, or null if not found.
const posts = await Post.findOne({ user: userId });
//this will not work because at this point 'posts' will be either a Document or null value
//So the object will not have the method 'findOne' available.
const foundPost = await posts.findOne({ "upload": post_id });
The solution is to deal correctly with the types of objects you have.
Below is a functional and safe implementation that solves your issue:
const userPosts = await Post.findOne({ user: userId }).exec();
if (!userPosts) {
// document not found with provided userId
return res.json({ success: false });
}
//here we have a Document
//check if document has 'posts' property and is an array
if (userPosts.posts) {
//filter the posts array
const filter = function(subDoc) {
return subdoc.upload === post_id
}
const subDocs = userPosts.filter(filter);
//filter returns an array, so we must check if has itens
//then we grab the first item
if (subDocs.length > 0) {
const foundPost = subDocs[0];
return res.json({ success: true, Post: foundPost });
}
//subDoc not found, return correct response
return res.json({ success: false });
}
If your Post model schema is what I'm supposing to be, this code will work perfectly.
const schema = mongoose.schema({
user: Number,
posts: [{ upload: Number }]
})
In case of error, please add the code of the model schema structure.
I have unique and required fields in my schema below.
An error is returned when any of them are left blank.
How do I find the paths in Error object with information on which fields were left blank?
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var Schema = mongoose.Schema;
var kullaniciShema = new Schema({
gsm: String,
email: String,
kullaniciAdi: { type: String, required: true, unique: true },
sifre: { type: String, required: true},
}, { collection: 'kullanici' });
kullaniciShema.plugin(uniqueValidator);
var Kullanici = mongoose.model('Kullanici', kullaniciShema);
module.exports = Kullanici;
Below is the controller where I save submitted data into the database. Some of the incoming data is empty, so I get an error for the fields that were left blank.
How do I find the incorrect fields in the Error object?
var yeniKullanici = new Kullanici({
gsm: req.body.gsm,
email: req.body.email,
kullaniciAdi: req.body.kullaniciAdi,
sifre: req.body.sifre,
});
yeniKullanici.save(function(err){
if (err) {
//var error=new err("hata");
//console.log(error.path);
selectAllFromKullanici((result) => {
//console.log(forUsersError.length);
forUsersError[forUsersError.length] = assert.equal(error.errors['ad'].message, 'Path `name` is required.');
res.render("kullanicilar", { kullanicilar: result, hata: forUsersError });
})
} else {
selectAllFromKullanici((result) => {
res.render("kullanicilar", { kullanicilar: result });
})
}
});
I'm sorry for my bad English.
I would check if the fields are empty BEFORE you try to run into a failure and extract data from an error message, which may fail on unknown errors.
I need to save a document to a mongo collection.
I want to save the 'insertedAt' and 'updatedAt' Date fields, so I suppose I can't do it in one step...
This is my last try:
my topic = new Topic(); // Topic is the model
topic.id = '123'; // my univocal id, !== _id
topic.author = 'Marco';
...
Topic.findOne({ id: topic.id }, function(err, doc) {
if (err) {
console.error('topic', topic.id, 'could not be searched:', err);
return false;
}
var now = new Date();
if (doc) { // old document
topic.updatedAt = now;
} else { // new document
topic.insertedAt = now;
}
topic.save(function(err) {
if (err) {
console.error('topic', topic.id, 'could not be saved:', err);
return false;
}
console.log('topic', topic.id, 'saved successfully');
return true;
});
});
But this way I end up duplicating records... :-(
Any suggestion?
Rather than doing whatever you are doing I prefer a very easy way to updating document with upsert. For this you need to keep in mind don't use the model to create an instance to insert. You need to create an object manually.
//don't put `updatedAt` field in this document.
var dataToSave = {
createdAt: new Date(),
id: 1,
author: "noor"
.......
}
Topic.update({ id: 123 }, { $set:{ updatedAt: new Date() }, $setOnInsert: dataToSave}, { upsert: true }, function(err, res){
//do your stuff here
})
This query will first check wether any document is there is the collection if yes then it will only update udpatedAt if not then it will insert the whole new document in the collection. Hope this answers your query.
set timestamps to false in schema definition and then add the fields on creation as you would like.
See sample schema definition below:
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var Topic = new Schema({
id:{
type:String,
required: true
},
author:{
type:String,
required: true
}
},{
timestamps: false
});
I try to use .findOneAndUpdate() to update my database.
No error message, but this part of the database is not updated with new data. The embedded document competitorAnalysisTextData is still empty.
// on routes that end in /users/competitorAnalysisTextData
// ----------------------------------------------------
router.route('/users/competitorAnalysisTextData/:userName')
// update the user info (accessed at PUT http://localhost:8080/api/users/competitorAnalysisTextData)
.post(function(req, res) {
console.log('1');
// Just give instruction to mongodb to find document, change it;
// then finally after mongodb is done, return the result/error as callback.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.firstObservation" : req.body.firstObservation,
"competitorAnalysis.secondObservation" : req.body.secondObservation,
"competitorAnalysis.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.brandName" : req.body.brandName,
"competitorAnalysis.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
Update
This is my "User" Schema part:
// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Require the crypto module for password hash
'use strict';
var crypto = require('crypto');
// create competitorAnalysisSchema
var CompetitorAnalysis = new Schema({
firstObservation: { type: String },
secondObservation: { type: String },
thirdObservation: { type: String },
brandName: { type: String },
productCategory: { type: String }
});
// create competitorAnalysisPhotoSchema
var CompetitorAnalysisPhoto = new Schema({
photo1: {type: String},
photo2: {type: String},
photo3: {type: String},
photo4: {type: String}
});
// create UserSchema
var UserSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
currentDemo: { type: String },
nextDemo: { type: String },
startTime: { type: String },
startLocation: { type: String },
arriveTime: { type: String },
arriveLocation: { type: String },
leaveTime: { type: String },
leaveLocation: { type: String },
competitorAnalysis: [CompetitorAnalysis],
competitorAnalysisPhoto: [CompetitorAnalysisPhoto],
created_at: Date,
updated_at: Date
});
// the schema is useless so far
// we need to create a model using it
var User = mongoose.model('User', UserSchema);
// make this available to our users in our Node applications
module.exports = User;
in javascript if you wish to update an object inside an array, you need to pick the index
var arr = [{name: "person1"},{name:"person2"}]
arr[0].name = "myname"
arr[1].name = "myFriend"
So it's the same in mongodb, check this link for detail example, or you can manually input the index, for quick hack.
User.findOneAndUpdate(
{ userName : req.params.userName},
{
$set:
{ "competitorAnalysis.0.firstObservation" : req.body.firstObservation,
"competitorAnalysis.0.secondObservation" : req.body.secondObservation,
"competitorAnalysis.0.thirdObservation" : req.body.thirdObservation,
"competitorAnalysis.0.brandName" : req.body.brandName,
"competitorAnalysis.0.productCategory" : req.body.productCategory
}
},
{ upsert: true },
function(err, user) {
// after mongodb is done updating, you are receiving the updated file as callback
console.log('2');
// now you can send the error or updated file to client
if (err)
return res.send(err);
return res.json({ message: 'User updated!' });
});
})
You should use the code above to update nested-array not to add to empty-array.
In javascript, if an array is still empty, we use .push() to add, while in mongodb the command is $push
var arr = []
arr.push({name:"person1"})
I got 3 database models in mongoose that looks like this:
//profile.js
var ProfileSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
matches: [{ type: Schema.Types.ObjectId, ref: 'Match' }]
});
//match.js
var MatchSchema = new Schema({
scores: [{ type: Schema.Types.ObjectId, ref: 'Score', required: true }],
});
//score.js
var ScoreSchema = new Schema({
score: {type: Number, required: true},
achivement: [{type: String, required: true}],
});
And I try to populate a profile with
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches')
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});
The matches get populated but I dont get the scores in matches to populate. Is this not supported in mongoose or do I do something wrong? Populate gives me this:
{
user_token: "539b07397c045fc00efc8b84"
username: "username002"
sex: 0
country: "SE"
friends: []
-matches: [
-{
__v: 1
_id: "539eddf9eac17bb8185b950c"
-scores: [
"539ee1c876f274701e17c068"
"539ee1c876f274701e17c069"
"539ee1c876f274701e17c06a"
]
}
]
}
But I want to populate the score array in the match array. Can I do this?
Yes, you are right. I tried using Chaining of populate I got same output.
For your query please use async.js and then populate by the method mentioned below.
For more details please have a look at this code snippet. It is a working, tested, sample code according to your query. Please go through the commented code for better understanding in the code below and the link of the snippet provided.
//Find the profile and populate all matches related to that profile
Profile.findOne({
_id: mongoose.Types.ObjectId(profile_id)
})
.populate('matches')
.exec(function(err, profile) {
if (err) throw err;
//We got the profile and populated matches in Array Form
if (profile) {
// Now there are multiple Matches
// We want to fetch score of each Match
// for that particular profile
// For each match related to that profile
async.forEach(profile.matches, function(match) {
console.log(match, 'match')
// For each match related to that profile
// Populate score achieved by that person
Match.find({
_id:match.id
})
.populate('scores', 'score')
.exec(function (err, score) {
if (err) throw err;
// here is score of all the matches
// played by the person whose profile id
// is passed
console.log(score);
})
})
}
});
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});