How can i update nested data by using mongoose findByIdAndUpdate - javascript

i can not able to update nested data in my mongodb. here is my "update" module at back-end side.
exports.updateOne = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const {id} = req.params;
console.log(req.body);
User.findByIdAndUpdate(id, req.body, { useFindAndModify: false, new: true}).populate('basic')
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update User with id=${id}. Maybe User was not found!`
});
} else
res.send({ message: "User was dupdated successfully." , data});
})
.catch(err => {
res.status(500).send({
message:
err.message || "Error updating User with id=" + id
});
});
};
and my front-end side is;
onChangePosition(e) {
const position = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
basic:
{
...prevState.currentStaff.basic,
position:position
}
}
}));
}
onChangeEmail(e) {
const emailBusiness = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
emailBusiness:emailBusiness
}
}));
}
updateStaff() {
StaffDataService.updateOne(
this.state.currentStaff.id,
this.state.currentStaff
).then(response => {
console.log(response.data);
})
.catch(e => {
console.log(e);
})
}
i can change state properly, and my sending data "req.body" is what i want (it is an object). There is no problem.
as you see above, i can update "email" because it is on the main body of object, but can not update "position" (nested element) because it is inside of basic (populated data).
i tried different methods by mongoose, and tried "$set" command.
Can anyone solve this?

To update, the nested value/object in your document, you should use dot notations, so it depends from the req.body variable value.
req.body shouldn't be a Mongoose doc. In such case you mongoose.toObject.
Second thing is:
[update] Object should be: field_with_subdocument.key_value: updated_propery
like this:
/** Example doc */
{
_id: 1,
parent_field: {
baby_field: value
}
}
/** Inside async function */
...await Model.findByIdAndUpdate(id, { "parent_field.baby_field": value })
Also, take a look at [`{overwrite: true}`](https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdate) option. It might be useful to you.

I faced the same issue, In my case, the defined mongoose schema for that model did not match the nested Object I was passing to the findByIdAndUpdate method. let me simplify it, here is the model
import { model, Schema } from 'mongooose';
const UserModel = model('user', new Schema({
profile: {
avatar: String,
bio: String,
}
}));
And here is the update query:
async function getDefaultProfile() {
const user = await UserModel.findById(process.env.DEFAULT_USER);
return user.profile;
}
const profile = await getDefaultProfile();
UserModel.findByIdAndUpdate('user id', {
$set: {
profile: profile
}
});
The important note was that my getDefaultProfile function returns a mongoose nested object, not a pure object. So in the returned object, I had $set, $get, etc function. So as you know this object is not what we define in the mongoose model, therefore the mongoose ignores it.
So I guess you have the same problem, or something close to my issue.
What should I do?
Run your project in debugging mode.
then check req.body or whatever that gives you the nested object (in my case getDefaultProfile).
Check it with your model, Are they equal?
And if that solution does not work for you, please try this solution, write a utility function:
export async function flatObjectAndSeparateThemByDot(
object: any,
): Promise<any> {
const res: any = {};
(function recurse(obj: any, current?: string) {
for (const key in obj) {
const value = obj[key];
// joined keys with dot
const newKey = current ? current + '.' + key : key;
if (value && typeof value === 'object') {
// it's a nested object, so do it again
recurse(value, newKey);
} else {
// it's not an object, so set the property
res[newKey] = value;
}
}
})(object);
return res;
}
then you can pass your nested object to this function and you will get something like this: { "profile.avatar": "lorem ipsum", "profile.bio": "bio temp" }. So to show you how this function works I will write a sample code:
const sampleProfile = {
profile: {
avatar: "asd",
bio: "sample"
}
}
const profile = await flatObjectAndSeparateThemByDot(sampleProfile);
await UserModel.findByIdAndUpdate('user id', {
$set: {
// other fields,
...profile
}
});

Related

NextJS fetching nested array of objects from MongoDB using mongoose and getServerSideProps

I'm trying to fetch an array of objects from MongoDB, using mongoose and SSP. One hitch is that all ObjectIds must be converted into strings. Currently I'm doing it like this:
export async function getServerSideProps({ query }) {
try {
const { user } = query
await connectDB()
const currentUser = await User.findOne({ user }).lean(),
{ _id } = await currentUser,
userProperties = await Property.find({ ownerId: _id }).lean()
currentUser._id = currentUser._id.toString()
userProperties.forEach(props => {
props._id = props._id.toString()
props.ownerId = props.ownerId.toString()
props.subarray.forEach(props => {
props._id = props._id.toString()
})
})
if (!currentUser) {
return {
notFound: true
}
}
return {
props: {
currentUser,
userProperties
}
}
} catch (err) {
console.log(err)
return {
redirect: {
destination: '/',
statusCode: 307
}
}
}
}
This produces: Error: If(...): Nothing was returned from render. I can fetch user without properties, no propblem, and I can console log the attached properties even though nothing is returned. What's happening here?
In order to send an array from getServerSideProps(), you should first transform it to a series of bytes with JSON.stringify, remember that HTTP sends text only. Re-transform as an array of objects in your React component with JSON.parse, with this method, you don't need to create the strings manually.

findOneAndUpdate mongoDB not returning properly

I am trying to push a user's choice as a string to their array of choices and return the updated document.
The route and function work successfully however it returns the User with an empty choice array. I believe the problem lies somewhere in the controller function but I cannot figure it out.
Any help is greatly appreciated!
To help, here is a screenshot of my console where you can see an empty choice array being returned.
Here is an image of my console.log
This is where I call the function
handleAnswerInput = (question) => {
let answerTextSelected = question.Text;
let answerTypeSelected = question.Type;
let usersName = this.state.user._id
this.setState({
count: this.state.count + 1
})
saveUserandScore(usersName, answerTextSelected)
.then(
this.loadQuestion(this.state.count)
)
console.log(answerTextSelected)
console.log(answerTypeSelected)
};
This is the controller function (updated from suggestions)
const saveUserAndTheirScore = (req, res) => {
let filter = { _id: req.params.id }
// let update = { choices: req.params.answer] }
console.log(req.params.id)
console.log(req.params.answer)
User.update(
{ filter },
{
$push: { choices: req.params.answer }
},
{
returnOriginal: false,
},
)
.then(dbData => res.json(dbData))
.catch(err => {
console.log(err);
res.json(err);
});
};
here is the axios call
export const saveUserandScore = (id, answer) => {
return axios.post(`/api/user/${id}/${answer}`);
};
you need to change user schema, in that you might have defined choices type as string. It must be an array.
findOneAndUpdate(filter, update, options, callback) has a returnOriginal option, if set to true (which is the default), it will return the document BEFORE the update. In your case, you might want to set it to false [1].
Unfortunately, the respective option for mongoose is named new [2].
[1] https://mongodb.github.io/node-mongodb-native/3.4/api/Collection.html#findOneAndUpdate
[2] https://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate

Issues with scope in try/catch while using async/await

My issue is that (seemingly) things are going out of scope, or the scope is being polluted when I enter my catch block in the function below:
export const getOne = model => async (req, res, next) => {
let id = req.params.id
let userId = req.user
try {
let item = await model.findOne({ _id: id, createdBy: userId }).exec()
if (!item) {
throw new Error('Item not found!')
} else {
res.status(200).json({ data: item }) // works perfectly
}
} catch (e) {
res.status(400).json({ error: e }) // TypeError: res.status(...).json is not a function
// also TypeError: next is not a function
// next(e)
}
}
Interestingly enough, using res.status(...).end() in the catch block works just fine, but it bothers me that I am not able to send any detail back with the response. According to the Express Documentation for res.send() and res.json I should be able to chain off of .status(), which, also interestingly enough, works just fine in the try statement above if things are successful - res.status(200).json(...) works perfectly.
Also, I tried abstracting the error handling to middleware, as suggested on the Express documentation, and through closures, I should still have access to next in the catch statement, right? Why is that coming back as not a function?
Why does res.status(...).json(...) work in my try but not catch block?
Why is next no longer a function in the catch block?
Thanks in advance!
Edit
This is failing in unit tests, the following code produces the errors described above:
describe('getOne', async () => {
// this test passes
test('finds by authenticated user and id', async () => {
expect.assertions(2)
const user = mongoose.Types.ObjectId()
const list = await List.create({ name: 'list', createdBy: user })
const req = {
params: {
id: list._id
},
user: {
_id: user
}
}
const res = {
status(status) {
expect(status).toBe(200)
return this
},
json(result) {
expect(result.data._id.toString()).toBe(list._id.toString())
}
}
await getOne(List)(req, res)
})
// this test fails
test('400 if no doc was found', async () => {
expect.assertions(2)
const user = mongoose.Types.ObjectId()
const req = {
params: {
id: mongoose.Types.ObjectId()
},
user: {
_id: user
}
}
const res = {
status(status) {
expect(status).toBe(400)
return this
},
end() {
expect(true).toBe(true)
}
}
await getOne(List)(req, res)
})
})
Why does res.status(...).json(...) work in my try but not catch block?
Seems like you're passing a non-express object that only has status & end methods when running using the unit testing. That's why it fails to find the json method

How to return a model back (or anything for that matter) on an update mutation in Sequelize?

I am using the update model method to update an entry in Sequelize using the mysql2 dialect. It seems as if with MySQL you can only return a count of rows affected. I would like to pass it back as a boolean.
It should be noted that I am fairly new to JS/ES6, Node, pretty much all of this, I am more versed in PHP so I apologize in advance if the answer is obvious or a fundamental misunderstanding of JS, but I promise I have worked on this for hours. The online documentation and online examples for the update method are fairly light.
The closest answer I can find is at: Sequelize on GraphQL update not return anything
I have removed irrelevant bits of this code for brevity.
Model:
export default (sequelize, DataTypes) => {
const Payment = sequelize.define('payment', {
...
processed: {
type: DataTypes.INTEGER(1).UNSIGNED,
allowNull: false,
defaultValue: '0'
},
...
return Payment;
Schema:
...
type Payment {
id: Int!
amount: Float!
processedTime: Int
processed: Int!
dueDate: Int!
status: PaymentStatuses
Employee: Employee
Client: Client!
Merchant: Merchant
PaymentType: PaymentType
}
type Query {
getPayment(id: Int!): Payment
}
type Mutation {
updatePaymentStatus(id: Int!, processed: Int!): Boolean
}
What I was originally trying to do was:
updatePaymentStatus(id: Int!, processed: Int!): Payment!
Resolver:
Mutation {
updatePaymentStatus: async (parent, {id, processed}, {models}) =>
{await models.Payment.update({processed}, {where: {id}})
.then((datareturned) => {return datareturned[0]})}
}
If I check my database, the update is actually working correctly. If I console.log(datareturned[0]) I get a 1 (or 0 if no rows were updated).
I would like to be able to return at least a Boolean as described in my schema but no matter what I try, I get null or some other error because the returned value is null. I realize I may be incorrectly assuming returning 1 will be assumed as true but I have also tried:
.then((datareturned) => {if (datareturned[0] === 1) {return true}})}
In GraphiQL:
mutation{
updatePaymentStatus(id:1, processed:0)
}
Response:
{
"data": {
"updatePaymentStatus": null
}
}
I am not sure if the problem is with my lack of JS knowledge, or that Sequelize does not return much using the MySQL dialect, or somewhere in between.
Hey i just ran into this last night... i did it like this in my resolver
addUserProfileCertification: async (_, args, { models }) => {
try {
await models.ProfileCompanyCertification.create(args.input);
return true;
} catch (err) {
logger.error(err);
}
return false;
},
and my mutation
addUserProfileCertification(input: ProfileCertificationInput!): Boolean
GraphiQL result looks like
{
"data": {
"addUserProfileCertification": true
}
}
You can easily return the whole model if you want though by return a findOne with the id from your args at the bottom of your resolver like...
updateUserProfileContact: async (_, args, { models }) => {
let returnValue = null;
const { id, input } = args;
try {
await models.ProfileContact.update(input, { where: { id } });
returnValue = await models.ProfileContact.findOne({ where: { id } });
} catch (err) {
logger.error(err);
}
return returnValue;
},
You just need to tell your mutation to expect a Payment type rather than a Boolean, and you can refine what you want to pass back to the client by selecting only certain fields from payment as part of the mutation that you enter into GraphiQL
This eventually ended up working for me, changing the Resolver for this to:
updatePaymentStatus: async (parent, {id, processed}, {models}) => {
try {
const foundPayment = await models.Payment.findByPk(id)
if (foundPayment){
models.Payment.update({processed: id}, {where: {id: id}})
return true;
}
else{
return false;
}
}
catch (e) {
console.log(e);
return false;
}
},
I wanted to be sure the payment id existed before running the update because the update would run regardless if the row existed or not. I'm not sure if I even need the try catch, but I left it in there just in case.

Update fields dynamically in postgres without using the || operator

I have this function that updates a table accordingly. I have used the pipe (||) operator already to pick the new values or retain the old ones if nothing was supplied in the request body for that field but I realized this method may not be maintainable if the columns are much. Hence, is there a way one can just extract only the fields that are supplied into the request body and then inject them into their respective columns without using the pipe (||)?
static async updateProduct(req, res) {
const { id } = req.authData.payload;
const {
description, category, imageurl, quantity, unitPrice
} = req.body;
try {
const { rows } = await db.query(queryProductAdmin, [req.params.productId, id]);
if(!rows[0]) {
return res.status(404).json({
status: 'Fail',
message: 'The product is not available'
});
}
**/** Instead of using the pipe, how can something like this be achieved?
.update(req.body, { fields: Object.keys(req.body) })*/**
const values = [
description || rows[0].description,
category || rows[0].category,
imageurl || rows[0].imageurl,
quantity || rows[0].quantity,
unitPrice || rows[0].unitPrice,
req.params.productId,
id
];
const response = await db.query(updateProductQuery, values);
const updatedProduct = response.rows[0];
return res.status(200).json({
message: 'Update was successful',
updatedProduct
});
} catch(error) {
const { message } = error;
return res.status(500).json({
status: 'Fail',
message
});
}
N.B: The code is working fine. I just need ideas on how to update dynamically.
You could do something like:
const values = ['description', 'category', 'imageurl', 'unitPrice']
.map(v => req.body[v] || rows[0][v])
.push(req.params.productId, id)
To go a bit further you could get the table fields using postgres API and map those instead depending on whether or not you are showing all the fields in the query array or not

Categories

Resources