Edit operation failing in Node - javascript

This is my code:
router.post('/update-posting', (req, res, next) => {
Account.findById(req.user._id)
.then(doc => {
var type = [];
if (req.body.full !== undefined) {
type.push('full');
}
if (req.body.part !== undefined) {
type.push('part');
}
if (req.body.seasonal !== undefined) {
type.push('seasonal');
}
if (req.body.temporary !== undefined) {
type.push('temp');
}
var title = req.body.title;
var salary = req.body.salary;
var timeline = req.body.timeline;
var experience = req.body.experience;
var description = req.body.description;
var duties = req.body.duties;
doc.postings[req.body._id] = {
_id: req.body._id,
title: title,
type: type,
salary: salary,
timeline: timeline,
description: description,
duties: duties,
experience: experience,
};
doc.save(r=>console.log(r));
})
.then(() => res.redirect('/employer/booth-edit'))
.catch(e => console.log(e))
});
And here's the model:
var mongoose = require('mongoose');
var plm = require('passport-local-mongoose');
var accountSchema = new mongoose.Schema({
// username (comes with passport): email; -> just for reference.
accType: String,
fullName: String,
displayName: String,
companyName: String,
contactPersonFullName: String,
companyWebsite: String,
city: String,
province: String,
postalCode: String,
phoneNumber: String,
hiringRegion: [], // TODO
description: String,
logo: [],
workingWithEOESC: Boolean,
industry: String,
phone: String,
ageGroup: String,
education: String,
lookingForWork: String,
employmentStatus: String,
resume: [],
mainWorkExp: String,
boothVisits: Number,
postings: []
});
accountSchema.plugin(plm);
module.exports = mongoose.model('account', accountSchema);
What I'm doing is trying to update an object in the postings array. Now here's the weird part. When I console log the result before doc.save() I get the updated version... And when I console log the response from doc.save() I get null... I'm sure that's a small bug but I cannot see it anywhere.
All fields are coming from the req.body object correctly.
Here are the logs.
Original object:
{ _id: 0,
title: 'Web developer',
type: [ 'full', 'seasonal' ],
salary: '14$',
timeline: '3 months',
description: 'tada',
duties: 'tada',
experience: '5 years' }
Updated object:
{ _id: '0',
title: 'Car mechanic',
type: [ 'part', 'temp' ],
salary: '50$',
timeline: '2 weeks',
description: 'desc',
duties: 'resp',
experience: '4 years' }
doc.save() response:
null
What's more interesting, this is the code I'm using for "creating" a job posting. It's almost the same code, and it works perfectly well:
router.route('/add-posting')
.get((req, res, next) => {
res.render('users/employer/add-posting', {
title: 'Employer Booth - Add Job Posting',
user: req.user
});
})
.post((req, res, next) => {
// Determining type of work.
var type = [];
if (req.body.full !== undefined) {
type.push('full');
}
if (req.body.part !== undefined) {
type.push('part');
}
if (req.body.seasonal !== undefined) {
type.push('seasonal');
}
if (req.body.temporary !== undefined) {
type.push('temp');
}
var title = req.body.title;
var salary = req.body.salary;
var timeline = req.body.timeline;
var experience = req.body.experience;
var description = req.body.description;
var duties = req.body.duties;
Account.findById(req.user._id)
.then(doc => {
doc.postings.push({
_id: doc.postings.length,
title: title,
type: type,
salary: salary,
timeline: timeline,
description: description,
duties: duties,
experience: experience,
});
doc.save();
})
.then(() => res.redirect('/employer/booth-edit'))
.catch(e => console.log(e));
});

They way you're doing it may work. But mongo has already provided you with update or findOneAndupdate methods. I'd suggest you to use them.
Your query would be easy to understand and debug when needed.
Try something like
db.collection.update({_id: .user._id},{$push :{postings: yourObject}})

I just remembered I had this issue before. Turns out that to update an array we need to do this:
doc.postings.set(req.body._id, {
_id: req.body._id,
title: title,
type: type,
salary: salary,
timeline: timeline,
description: description,
duties: duties,
experience: experience,
});
I remember reading an issue on that. Will add the link if I find it again.
We need to use the .set method instead of the = operator.

Related

Mongoose - CastError Cast to string failed for value "Object"

I have Mongoose CastError issue. I made a nodeJs API. At the specific route, it returns data appended with some other data. I saw many fixes available here but my scenario is different.
Here is my model and the problem occurs at fields property.
const deviceSchema = new Schema({
device_id: { type: String, required: true },
user_id: { type: Schema.Types.ObjectId, ref: 'User', require: true },
location_latitude: { type: String, default: '0' },
location_longitude: { type: String, default: '0' },
fields: [{ type: String }],
field_id: { type: Schema.Types.ObjectId, ref: 'Field', required: true },
timestamp: {
type: Date,
default: Date.now,
},
});
and my controller is
exports.getAllDevices = async (req, res) => {
try {
let devices = await Device.find({})
.sort({
timestamp: 'desc',
})
.populate('user_id', ['name']);
// Let us get the last value of each field
for (let i = 0; i < devices.length; i++) {
for (let j = 0; j < devices[i].fields.length; j++) {
if (devices[i].fields[j] !== null && devices[i].fields[j] !== '') {
await influx
.query(
`select last(${devices[i].fields[j]}), ${devices[i].fields[j]} from mqtt_consumer where topic = '${devices[i].device_id}'`
)
.then((results) => {
************** Problem occurs here **************
if (results.length > 0) {
devices[i].fields[j] = {
name: devices[i].fields[j],
last: results[0].last,
};
} else {
devices[i].fields[j] = {
name: devices[i].fields[j],
last: 0,
};
}
************** Problem occurs here **************
});
}
}
}
// Return the results
res.status(200).json({
status: 'Success',
length: devices.length,
data: devices,
});
} catch (err) {
console.log(err);
res.status(500).json({
error: err,
});
}
};
It actually gets data from InfluxDB and appends it to fields property which was fetched from MongoDB as mentioned in my model. But it refused to append and CastError occurs.
After addition, it will look like this
I can't resolve this error after trying so many fixes. I don't know where I'm wrong. Please suggest to me some solution for this.
I can see you are not using devices variable as Mongoose Document. devices is an array of Documents.
I would like to suggest you to use lean() function to convert from Document to plain JavaScript object like
let devices = await Device.find({})
.sort({
timestamp: 'desc',
})
.populate('user_id', ['name'])
.lean();

Promise not getting resolved inside reduce array method javascript

I have large array of objects and filtered the objects based on the userID. Here is the code below.
const filteredArr = LargeArr.Items.reduce(
async(acc, { attributes: { dob, name, picture} = { dob: null, name: null, picture: null }, userID }) => {
let pic = null;
if (picture) { pic = await getPic(picture); } // async here
acc[userID] = { name, userID, pic, dob };
return acc;
}, {});
Expected Output :
{
'1595232114269': {
name: 'Mark Status',
userID: '1595232114269',
picture: 'mark-status.jpg',
dob: '2020-08-10'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d23d': {
name: 'Jack Thomas',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d23d',
picture: 'jack-thomas.jpg',
dob: '1990-12-20'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d47p': {
name: 'Petro Huge',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d47p',
picture: 'petro huge.jpg',
dob: '1856-12-20'
},
'48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d55j': {
name: 'Mark Henry',
userID: '48e69555d778f9b9a3a1d553b9c3b8f7dd6a3394ac82df1433b60a69c055d55j',
picture: 'mark-henry.jpg',
dob: '2005-12-29'
}
}
I need to get picture from an api which is asynchronous, so used async await inside the reduce method. The problem here is it is always showing as Promise pending. If this was an array of object, then i can return Promise.all, but since this is object containing object how can i proceed with this inside reduce method? I need the exact same expected output.
Can somebody help me with this? Any help would be really appreciated.
To use reduce while iterating over items asynchronously, you'd have to have the accumulator which gets passed from callback to callback to be a Promise. While this is possible, it'll make things pretty difficult to read, and introduces some unnecessary syntax noise.
Use a plain for loop instead:
const filteredArr = {};
for (const item of LargeArr.Items) {
const { attributes: { dob, name, picture} = { dob: null, name: null, picture: null } } = item;
const pic = picture ? await getPic(picture) : null;
filteredArr[userID] = { name, uesrID, pic, dob };
}
If you really wanted to take the reduce route:
LargeArr.Items.reduce(
(acc, { attributes: { dob, name, picture} = { dob: null, name: null, picture: null }, userID }) => {
return acc.then(async (acc) => {
let pic = null;
if (picture) { pic = await getPic(picture); } // async here
acc[userID] = { name, userID, pic, dob };
return acc;
});
}, Promise.resolve({})
)
.then((filteredArr) => {
// do stuff with filteredArr
});
Unless the getPic calls need to be made in serial, you could consider using Promise.all instead, to iterate through the whole array at once, rather than waiting on the resolution of the prior Promise before going onto the next.
If your API can handle Promise.all:
const filteredArr = {};
await Promise.all(LargeArr.Items.map(async (item) => {
const { attributes: { dob, name, picture} = { dob: null, name: null, picture: null } } = item;
const pic = picture ? await getPic(picture) : null;
filteredArr[userID] = { name, uesrID, pic, dob };
}));

How to update existing object with additional data

The project is created with nodejs and mongoose. What I am trying to do is to update the existing model with addition data (which is a comment, in that case).
This is the model and its methods:
const bugSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
date: {
type: String,
required: true
},
time: {
type: String,
required: true
},
assignedTo: {
type: String,
required: true
},
assignedBy: {
type: String,
required: true
},
status: {
type: String,
required: true
},
priority: {
type: String,
required: true
},
comments: {
comment:[
{
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
}
]
}
});
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
const updatedComments = [...this.comments];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
The controller, which is passing the information from the form:
exports.postComment = (req,res,next) =>{
const bugId = req.body.bugID;
const name = req.session.user.fullName;
const content = req.body.content;
const prod = {name, content};
Bug.findById(bugId).then(bug =>{
return bug.addComment(prod);
})
.then(result =>{
console.log(result);
});
};
I am getting a following error:
(node:3508) UnhandledPromiseRejectionWarning: TypeError: this.comments is not iterable
(node:3508) UnhandledPromiseRejectionWarning: TypeError: this.comments is not iterable
The error indicate you're trying to iterable a type of data which does NOT has that capability.
You can check that printing the type:
console.log(typeof this.comments)
Or even, priting the whole object:
console.log(this.comments)
as you can see, in both cases you're getting an object, not a list (how you spect)
So you can do 2 things:
1- Iterable a list
this.comments is an object but into that object you have the list you want, so just use the list instead.
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
//const updatedComments = [...this.comments];
const updatedComments = [...this.comments.comment];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
Or you can modify your schema making the comments a list instead of an object
2- comments as list in schema
Define the comments attribute as a list
const bugSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
...
...,
comments:[
{
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
}
]
});
And then, try to iterable it as how you been doing
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
const updatedComments = [...this.comments];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
I am not sure but comments is an object and not an array so you can't push using [...this.comments] and I think it is the comment you want to push?
const updatedComments = [...this.comment];
updatedComments.push({
user : username,
content: content
});
this.comment = updatedComments;
From your schema comments is not an array. you are trying to spread an object into an array. const updatedComments = [...this.comments]; also push works on array.
try to modify your schema definitions by declaring the commentSchema outside the bugSchema.
const commentSchema = new Schema({
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
})
const bugSchema = new Schema({
comments: {
type: [commentSchema]
}
})
Bug.findByIdAndUpdate(bugId, {$push: {comments: newComment}})
Don't use findByIdAndUpdate Mongoose method, you better use save
it is written here https://mongoosejs.com/docs/tutorials/findoneandupdate.html
The findOneAndUpdate() function in Mongoose has a wide variety of use cases. You should use save() to update documents where possible, but there are some cases where you need to use findOneAndUpdate(). In this tutorial, you'll see how to use findOneAndUpdate(), and learn when you need to use it.
Below a router example
router.put('/items', (req, res) => {
if (!req.body._id || !req.body.title) {
return res.status(501).send({ message: 'Missing parameters, or incorrect parameters' });
}
return itemModel.findOne({ _id: req.body._id }, (err, item) => {
if (err) {
return res.status(500).send({
message: err
});
}
item.title = req.body.title; // <------------- You rewrite what was before stored on title attribute
return item.save((err, item) => { // <------------- You save it, this is not gonna create a new one, except if it doesn't exist already
if (err) {
return res.status(400).send({
message: 'Failed to update item'
});
} else {
return res.status(200).send({
message: 'Item update succesfully',
data: item
});
}
});
});
});

Exception while simulating the effect of invoking 'deals.insert' error

I'm trying to submit form data to the database with react and meteor.
I have a AddDeal component for the form and a collection for the deals and also a method inside it.
Error
Exception while simulating the effect of invoking 'deals.insert'
ReferenceError: _id is not defined
Getting the error: ID is required when submit is clicked.
I don't know how to handle the _id when inserting.
Here is my code, and thanks for helping!
onSubmit(e) function
onSubmit(e) {
e.preventDefault();
const title = this.state.title.trim();
const description = this.state.description;
const category = this.state.category;
const location = this.state.location;
const price = this.state.price.trim();
e.preventDefault();
if (title, description, category, location, price) {
Meteor.call('deals.insert', title, description, category, location, price);
}
alert('Title is: ' + this.state.title + 'Description is: ' + this.state.description + 'Category is: ' + this.state.category
+ 'Location is: ' + this.state.location + 'Price: ' + this.state.price);
this.setState({
title: '',
description: '',
category: 'technology',
location: 'USA',
price: '0.00'
});
}
Insert method
export const Deals = new Mongo.Collection('deals');
if (Meteor.isServer) {
Meteor.publish('deals', function () {
return Deals.find({ userId: this.userId });
});
}
Meteor.methods({
'deals.insert'(_id, title, description, category, price, location) {
if (!this.userId) {
throw new Meteor.Error('not-allowed');
}
new SimpleSchema({
_id: {
type: String,
min: 1
},
title: {
type: String,
optional: true
},
description: {
type: String,
optional: true
},
category: {
type: String,
optional: true
},
location: {
type: String,
optional: true
},
price: {
type: Number,
optional: true
}
}).validate({
});
Deals.insert({
_id,
title,
description,
category,
location,
price,
createdAt: Date(),
userId: this.userId
});
}
});
On deals.insert you are validating the parameter this.userId instead of this._id?
I think you nedd to change this:
'deals.insert'(_id, title, description, category, price, location) {
if (!this.userId) {
throw new Meteor.Error('not-allowed');
}
...
to this:
'deals.insert'(_id, title, description, category, price, location) {
if (!this._id) {
throw new Meteor.Error('not-allowed');
}

Mongoose schema/methods disappearing when overwriting subdocument

I have a schema with a method - 2 variations. They both work fine when adding an address the firs time but version one will blow up when adding another address (i.e. when it goes through the for loop). What I mean by blowing up is that it seems to destroy my order instance - there is no more 'save' method.
The schema
var Address = new Schema({
type: { type: String, enum: ['shipping', 'billing'] },
street: { type: String, required: true },
city: { type: String, required: true },
region: String,
country: String,
postal: { type: String, required: true },
});
var Order = new Schema({
email: {
type: String
},
address: [Address]
});
Now if I have added an addAddress() method to my schema. Here are the 2 versions I have tried.
// Version 1 - has issues on subsequent call
Order.methods.addAddress = function() {
var data = { type: 'shipping', city: 'Tempe', postal: '85281', street: '420 Mill Ave'};
for(var i = this.address.length-1; i >=0; i--) {
if(this.address[i].type === type) {
delete address[i];
}
}
this.address.push(data);
}
// Version 2 - works fine
Order.methods.addAddress = function() {
var data = { type: 'shipping', city: 'Tempe', postal: '85281', street: '420 Mill Ave'};
var found = false;
for(var i = this.address.length-1; i >=0; i--) {
if(this.address[i].type === type) {
found = true;
this.address[i] = data;
}
}
if(!found)
this.address.push(data);
}
Trying to save after using V1 that will yield this error:
Uncaught Exception
TypeError: Object #<Object> has no method 'save'
at /var/node_modules/mongoose/lib/document.js:1270:13
at Array.forEach (native)
at model.pre.err.stack (/var/node_modules/mongoose/lib/document.js:1252:12)
at model._next (/var/node_modules/mongoose/node_modules/hooks/hooks.js:50:30)
at model.proto.(anonymous function) [as save] (/var/node_modules/mongoose/node_modules/hooks/hooks.js:96:20)
at Promise.<anonymous> (/var/controllers/cart.js:79:11)
at Promise.<anonymous> (/var/node_modules/mongoose/node_modules/mpromise/lib/promise.js:171:8)
at Promise.EventEmitter.emit (events.js:95:17)
at Promise.emit (/var/node_modules/mongoose/node_modules/mpromise/lib/promise.js:88:38)
at Promise.fulfill (/var/node_modules/mongoose/node_modules/mpromise/lib/promise.js:101:20)
Let's take the following code:
Order.findById(id.exec(function(err, o) {
o.addAddress('shipping', { street: '1000 Mill Ave', city: 'Tempe', postal: '85281' });
console.log(o);
o.save(function(err, order) {
});
})
Notice the console call? On each variation it appear that the order object is ok:
{
_id: 007d0000b10000000000000c,
address: [{
street: '420 Mill Ave',
postal: '85281',
city: 'Tempe',
type: 'shipping'
_id: 52b2459f1547a5e12300000b
}]
}
But it seems to have lost something such as the 'save' method.
Any ideas?
You're defining the address as a dict and not as an address object. Instead you should do something like this:
Order.methods.addAddress = function(type, data, next) {
// Have your removal code here
var self = this
var address = new Address(data)
address.save(function(err, address) {
if (err) {
...
} else {
self.address.push(address)
next()
}
})
}
Notice that I'm populating the address array with an address object as per the schema definition.

Categories

Resources