How can i combine two update requests into one? - javascript

I now have a code that works.
await Users.update({ selected: false }, { where: { userId: req.body.userId } });
await Users.update(
{
selected: req.body.selected,
descr: req.body.note
},
{
where:
{
entId: req.body.id,
userId: req.body.userId
}
}
);
But what if it is possible to combine these two queries into one? I need the 'selected' and 'note' field that I pass to change conditionally in the table. And all other 'selected' fields inherent to the user in the table became false.
Unfortunately, I did not find anything like that in the documentation. Thank you in advance for your help!

Unfortunately there is no such method like bulkUpdate in Sequelize so you need to call update twice and better to use a transaction to make these two queries as a one atomic operation.
await Sequelize.transaction(async transaction => {
await Users.update({ selected: false }, { where: { userId: req.body.userId }, transaction });
await Users.update(
{
selected: req.body.selected,
descr: req.body.note
},
{
where:
{
entId: req.body.id,
userId: req.body.userId
},
transaction
}
);
});

You can use the sequelize transaction and wrap it up inside try/catch,
// define transaction outside the try/catch so you can rollback if needed
const transaction = await sequelize.transaction();
try {
await Users.update({ selected: false }, { where: { userId: req.body.userId }, transaction })
.then((r) => r)
.catch((e) => {
throw e;
});
await Users.update(
{
selected: req.body.selected,
descr: req.body.note
},
{
where: {
entId: req.body.id,
userId: req.body.userId
},
transaction
}
)
.then((r) => r)
.catch((e) => {
throw e;
});
// always call commit at the end
await transaction.commit();
return true;
} catch (error) {
// always rollback
await transaction.rollback();
console.log(error);
throw error;
}

Related

Create and update in the same sequelize transaction

I want to insert a new row and immediately update it, all within the same sequelize transaction, is this possible?
My code so far:
let transaction;
try {
transaction = await dbcontext.sequelize.transaction();
const newRole = await dbcontext.Role.create({
name: "New Role"
}, { transaction });
await dbcontext.Role.update(
{
name: "New name",
},
{
where: {
id: newRole.id,
},
}, { transaction }
);
await transaction.commit();
console.log("Role commited");
} catch (error) {
console.log("Rollback in progress");
if (transaction) {
await transaction.rollback();
}
console.log(error);
}
update has only two parameters so the transaction option should be indicated right next to the where option:
await dbcontext.Role.update(
{
name: "New name",
},
{
where: {
id: newRole.id,
},
transaction,
}
);

MongoDB/Mongoose $pull an Object in an Array where _id of the Object is matching

I have this Schema here
Consider the likedTours which is an Array of Objects (Tours) (ignore position 0).
I want to pull any Objects where the _id of a Tour matches the critiria.
Adding a new Tour upon liking a tour is okay, but on unlike I don't know how to pull that item out.
Here is my function in the Controller in the Node.JS backend
const unlikeTour = async (req, res) => {
try {
TourDB.Tour.findOneAndUpdate(
{ _id: req.params.tourid },
{
$pull: { likedUsers: req.userID },
$inc: { likes: -1 },
}
).exec(async (err, docs) => {
if (!err) {
try {
await UserDB.User.findOneAndUpdate(
{ _id: req.userID },
{ $pull: { 'likedTours._id': docs._id } } //Here I need help
).exec()
return res.status(200).send({ successMessage: 'Tour successfully unliked' })
} catch (err) {
return res.status(500).send({ errorMessage: 'User not found' })
}
} else {
return res.status(500).send({ errorMessage: 'Tour not found' })
}
})
} catch (err) {
return res.status(500).send({ errorMessage: err })
}
}
This method looks for a tour and update it by pulling out the userID and decrement the likes count by -1.
And then I try to find in the UserDB that tour in the likedTours and tried to pull but it doesn't not work like that.
Thanks in advance
you can update as
await UserDB.User.findOneAndUpdate(
{ _id: req.userID },
{ $pull: { likedTours: { _id: docs._id } } } //Here I need help
).exec();
reference: https://docs.mongodb.com/manual/reference/operator/update/pull/

Node.js - How to use Sequelize transaction

I am a beginner with sequelize and cannot get the transactions to work. Documentation is unclear and makes the following example not able to adapt to my requirements.
return sequelize.transaction(t => {
// chain all your queries here. make sure you return them.
return User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, {transaction: t}).then(user => {
return user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, {transaction: t});
});
}).then(result => {
// Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback
}).catch(err => {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback
});
First I have to insert a tuple in 'Conto', then insert another tuple in 'Preferenze' and finally based on the 'tipo' attribute insert a tuple in 'ContoPersonale' or 'ContoAziendale'.
If only one of these queries fails, the transaction must make a total rollback, commit.
The queries are:
Conto.create({
id: nextId(),
mail: reg.email,
password: reg.password,
tipo: reg.tipo,
telefono: reg.telefono,
idTelegram: reg.telegram,
saldo: saldoIniziale,
iban: generaIBAN()
})
Preferenze.create({
refConto: 68541
})
if (tipo == 0) {
ContoPersonale.create({
nomeint: reg.nome,
cognomeint: reg.cognome,
dataN: reg.datan,
cf: reg.cf,
refConto: nextId()
})
}
else if (tipo == 1) {
ContoAziendale.create({
pIva: reg.piva,
ragioneSociale: reg.ragsoc,
refConto: nextId()
})
}
With a transaction you pass it to each query you want to be part of the transaction, and then call transaction.commit() when you finished, or transaction.rollback() to roll back all the changes. This can be done use thenables however it is clearer when using async/await.
Since none of your queries depend on each other you can also make them concurrently using Promise.all().
thenables (with auto commit)
sequelize.transaction((transaction) => {
// execute all queries, pass in transaction
return Promise.all([
Conto.create({
id: nextId(),
mail: reg.email,
password: reg.password,
tipo: reg.tipo,
telefono: reg.telefono,
idTelegram: reg.telegram,
saldo: saldoIniziale,
iban: generaIBAN()
}, { transaction }),
Preferenze.create({
refConto: 68541
}, { transaction }),
// this query is determined by "tipo"
tipo === 0
? ContoPersonale.create({
nomeint: reg.nome,
cognomeint: reg.cognome,
dataN: reg.datan,
cf: reg.cf,
refConto: nextId()
}, { transaction })
: ContoAziendale.create({
pIva: reg.piva,
ragioneSociale: reg.ragsoc,
refConto: nextId()
}, { transaction })
]);
// if we get here it will auto commit
// if there is an error it with automatically roll back.
})
.then(() => {
console.log('queries ran successfully');
})
.catch((err) => {
console.log('queries failed', err);
});
async/await
let transaction;
try {
// start a new transaction
transaction = await sequelize.transaction();
// run queries, pass in transaction
await Promise.all([
Conto.create({
id: nextId(),
mail: reg.email,
password: reg.password,
tipo: reg.tipo,
telefono: reg.telefono,
idTelegram: reg.telegram,
saldo: saldoIniziale,
iban: generaIBAN()
}, { transaction }),
Preferenze.create({
refConto: 68541
}, { transaction }),
// this query is determined by "tipo"
tipo === 0
? ContoPersonale.create({
nomeint: reg.nome,
cognomeint: reg.cognome,
dataN: reg.datan,
cf: reg.cf,
refConto: nextId()
}, { transaction })
: ContoAziendale.create({
pIva: reg.piva,
ragioneSociale: reg.ragsoc,
refConto: nextId()
}, { transaction })
]);
// if we get here they ran successfully, so...
await transaction.commit();
} catch (err) {
// if we got an error and we created the transaction, roll it back
if (transaction) {
await transaction.rollback();
}
console.log('Err', err);
}

Cancel Promise.all update MongoDB when one reject

I have two Model: User and Project.
User: name, projects [ { id, role } ]
Project: name, members []
I wrote a function to add a member (from User) to Project. My code:
const addMember = async (req, res, next) => {
const { userId, role, projectId } = req.body
Promise.all([
Project.findByIdAndUpdate(projectId, { $push: {members: userId}}),
User.findByIdAndUpdate(userId, { $push: {projects: { id: userId, role: role}} })
]).then(values => {
if (!values[0] || !values[1]) return next("Can not find")
return res.json({
result: 'ok',
message: "Add member successfully!",
})
}).catch(error => next(error))
}
But it does not work as I expected. If projectId wrong, nested method 1 in Promise not working but nested method 2 still working, the database will be updated. The same as userId wrong.
How can I return error when once wrong?
If I am not wrong, you want to only update user when project update is successful, for that Promise.all is not the solution, chain the promises, like
Project.findByIdAndUpdate(projectId, { $push: {members: userId}})
.then(()=>{
return User.findByIdAndUpdate(userId, { $push: {projects: { id: userId, role: role}} });
})
.then(values => {
if (!values[0] || !values[1]) return next("Can not find")
return res.json({
result: 'ok',
message: "Add member successfully!",
})
}).catch(error => next(error))
Rather than calling findByIdAndUpdate functions in the
Promise.all block. You can split the code in to two parts.
1) Define findById function which only check if the record exists and an update function to update the entries
2) execute the findById and update codes in the Promise.all separately.
const addMember = async (req, res, next) => {
const { userId, role, projectId } = req.body
Promise.all([
Project.findById(projectId, { $push: {members: userId}}),
User.findById(userId, { $push: {projects: { id: userId, role: role}} })
]).then(values => {
if (!values[0] || !values[1]) return next("Can not find")
Promise.all([
Project.update(projectId, { $push: {members: userId}}),
User.update(userId, { $push: {projects: { id: userId, role: role}} })
]).then(values => {
return res.json({
result: 'ok',
message: "Add member successfully!",
})
})
}).catch(error => next(error))
}

The right way to do multiple MongoDB operations

If I need to perform two or three different operations on a few collections, is there a better way than chaining together find/update operations? For example:
db.collection('contactinfos').findOneAndUpdate(
{ _id: ObjectID(contactID) },
{ $set: { sharedWith } }
).then(response => {
db.collection('users').update(
{ _id: { $in: sharedWith.map(id => ObjectID(id)) } },
{ $addToSet: { hasAccessTo: contactID } },
{ multi: true }
).then(response => {
db.collection('users').update(
{ _id: { $in: notSharedWith.map(id => ObjectID(id)) } },
{ $pull: { hasAccessTo: contactID } },
{ multi: true }
).then(response => {
return res.send({ success: true });
}).catch(err => {
logger.error(`in updating sharing permissions for ${contactID} by user ${_id}`, err);
return res.status(400).send({ reason: 'unknown' });
});
}).catch(err => {
logger.error(`in updating sharing permissions for ${contactID} by user ${_id}`, err);
return res.status(400).send({ reason: 'unknown' });
});
}).catch(err => {
logger.error(`in updating sharing permissions for ${contactID} by user ${_id}`, err);
return res.status(400).send({ reason: 'unknown' });
});
It just seems messy and there has to be some better way of doing it. Furthermore, if there is an error after the first findOneAndUpdate that prevents the other updates from running, then there will be inconsistent data across documents. The documents contain ID references to other documents for faster lookup.
Also, is there a way to catch all errors within a chain of promises?
From your callback hell I can see you do not use response argument of .then() method anywhere. If you do not need results of one query to perform another, consider using Promise.all() method:
const updateContactInfo = db.collection('contactinfos')
.findOneAndUpdate(
{ _id: ObjectID(contactID) },
{ $set: { sharedWith } }
);
const updateUsers = db.collection('users')
.update(
{ _id: { $in: sharedWith.map(id => ObjectID(id)) } }, //hint: use .map(ObjectId) instead.
{ $addToSet: { hasAccessTo: contactID } },
{ multi: true }
);
const updateUsers2 = db.collection('users')
.update(
{ _id: { $in: notSharedWith.map(id => ObjectID(id)) } }, //hint: use .map(ObjectId) instead.
{ $pull: { hasAccessTo: contactID } },
{ multi: true }
);
Promise
.all([updateContactInfo, updateUsers, updateUsers2])
.then((values) => {
const updateContactInfoResult = values[0];
const updateUsersResult = values[1];
const updateUsers2Result = values[2];
return res.send({ success: true });
})
.catch((reason) => {
logger.error(`msg`, reason);
return res.status(400).send({ reason: 'unknown' });
});
Promise.all() will continue executing following .then() only if all the promises do resolve, otherwise it'll fall into the .catch() method. As of error handling, you can easily chain multiple .catch() methods, which is nicely explained here.
If you cannot have any data inconsistency, either:
Get some SQL database with transactions (easier solution)
Look into MongoDB Two-Phase Commit
And if it is acceptable to happen, let's say once per 1kk times, do include checking it's consistency within your app's logic.

Categories