how to rollback changes for an update query in mongoose - node.js

I made a query in mongodb for updating a document in a collection that include adding additional fields in that document.
Then i made another to update another document in another collection and executed in the result function of first query,but i got error while updating the second one.
I need to roll back changes made on the 1 st document.
please help

According to docs you can rollback a transaction in mongoose using session.abortTransaction()
Sample code from docs
let session = null;
return Customer.createCollection().
then(() => Customer.startSession()).
then(_session => {
session = _session;
session.startTransaction();
return Customer.create([{ name: 'Test' }], { session: session });
}).
then(() => Customer.create([{ name: 'Test2' }], { session: session })).
then(() => session.abortTransaction()).
then(() => Customer.countDocuments()).
then(count => assert.strictEqual(count, 0));

Related

Deleting object from mongoose database not working if site is not refreshed

app.delete("/api/persons/:id", (req, res, next) => {
Person.findByIdAndDelete(req.params.id)
.then((result) => {
res.status(204).end();
})
.catch((error) => next(error));
});
Not sure how to even explain this properly, but there is my delete method. It works fine for objects that are allready in the databases, but if I add a new one and I dont refresh the site, I get error: Cast to ObjectId failed for value "undefined" (type string) at path "_id" for model "Person"
Below is my mongoose schema if that helps:
const personSchema = new mongoose.Schema({
name: { type: String, required: true },
number: { type: Number, required: true },
});
personSchema.set("toJSON", {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString();
delete returnedObject._id;
delete returnedObject.__v;
},
});
My guess is you're optimistically updating your frontend with the new Person without waiting for a successful DB response with the new data. That is a valid technique, but gets you into trouble if you're not careful.
My suggestion would be to also send the new value from the database back to your app right away say it can stay in sync. You likely have no _id value on the front end if you're optimistically updating the app before a DB response.
Something like this:
app.post("api/person/new", async (req, res) => {
try {
const person = new Person(req.body)
await person.save()
res.status(201).send({ person })
} catch (err) {
res.status(500).send({ err })
}
})
And then more importantly, your API handler on the frontend needs to take that returned person value and use it to update the values on your front end, so it has access to the _id property for immediate deletion. And if there's an error creating the person for any reason, you can remove the person from the front end, or handle it however you wish.
I don't know what your app is built with, so I can write a sample bit of code for it.

How to update many documents on mongo and return that updated document?

How can I update many documents on mongoose and return those updated documents so I then I can pass the updated docs to a different service on my code? This is seems like something simple to do but I'm getting confused on my head on how to implement it
In my current code I just update the documents on batch with updateMany, but as the mongo documentation says the writeConcern returned is just the # of docs updated {n: 0 } not the actual documents.
Current Code:
const checkAndUpdateSubscription = () => {
const filter = {
"payments.0.stripeSubscriptionEndDate": { $lte: today },
hasPaid: true,
};
const update = { $set: { hasPaid: false, isSubscriptionNew: 0 } };
const options = { new: true, useFindAndModify: false };
return new Promise((resolve, reject) => {
ModelModel.updateMany(filter, update, options)
.then((response) => {
console.log('response inside checkAndUpdateSubscription', response)
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
I would like to change it to something similar to my pseudo code below.
What I would like to do:
const checkAndUpdateSubscription = () => {
const filter = {
"payments.0.stripeSubscriptionEndDate": { $lte: today },
hasPaid: true,
};
const update = { $set: { hasPaid: false, isSubscriptionNew: 0 } };
const options = { new: true, useFindAndModify: false };
return new Promise((resolve, reject) => {
// 1. ModelModel.find where stripeSubscriptionEndDate $lte than today ..etc
// 2. Update the document(s)
// 3. Return the updated document(s)
(//4.) .then(updatedModel => sendUpdateModelToOutsideService(updatedModel))
});
};
I don't know if this is necessary in the context of this question but the checkAndUpdateSubscription method is a function that runs every 1min in my db for all my users (# ~thousands)
You can do those as alternative solutions
(maybe there is simpler way, but those will work i think)
Find the ids
find to get those ids
ids = find(your_filter,project_keep_id_only), and then make ids=[_id1, _id2 ...] an array of the ids
update with filter _id $in ids
update({"_id" : {"$in" : ids}},your_update_set_etc)
find to get the updated documents
docs=find({"_id" : {"$in" : ids}})
*if ids are not too many it will be fine i think
Mark the updated with extra field
on update set one extra field with the update_id
after the update, do a find based on that update_id, to get the documents
and if you want your remove that extra field after
If this run parallel, this extra field could be an array, with many update_ids, that you remove them after you get those documents back.

Nodejs mongodb find and update multiple documents in transaction

I have a mongo 4.2 replica set. I have N processes running concurrently and trying to read a collection. This collection is like a queue. I'd like to read 100 elements and update them in a transaction so other processes won't try to read those.
My code goes:
const collection = client.db("test").collection(TEST_COLLECTION);
const session = client.startSession();
try {
let data = null;
await session.withTransaction(async () => {
console.log("starting transaction")
data = await collection.find({ runId: null }, { _id: 1, limit: 100 }).toArray();
const idList = data.map(item => item._id.toHexString());
await collection.updateMany(
{ runId: { $in: idList } },
{ $set: { runId: runId } },
{ session });
console.log("Successful transaction")
});
data.map(item => {
// process element one by one and update them (no need for transaction here)
})
} catch (e) {
console.error("The transaction was aborted due to an unexpected error: " + e);
} finally {
await session.endSession();
console.log("Closing transaction")
}
this is the code I've got right now. The thing is that find() won't accept options so I can't pass the session. This means it won't be part of the transaction.
the mongodb documentations states that: When using the drivers, each operation in the transaction must be associated with the session (i.e. pass in the session to each operation).
So I'm assuming that this is actually not transactional only the update part which not solves my problem. Is there any way to include both in my transaction? Any ideas on this? Other/better options?
Thanks
EDIT:
So I was staring at my question for 15 minutes when it hit me. If I update first using the transaction. Then querying with the runId even outside of the transaction I can achieve my goal. Am I right? Is it so easy?
EDIT2:
Edit1 was stupid now I can't limit to 100 items. Back to the start.
EDIT3:
I'am using native mongodb nodejs driver.
To use a find in a transaction, pass the session using the session method:
doc = await Customer.findOne({ name: 'Test' }).session(session);
See Transactions in Mongoose

Data is mutated but not updated in the database

I have a sever connected to a mongodb database. When I add a first level data and then save that, it works.
For example :
// this works fine
router.post('/user/addsomedata', async (req,res)=>{
try {
const user = await User.findOne({email : req.body.email})
user.username = req.body.username
await user.save()
res.send()
} catch(e) {
res.status(404).send(e)
}
})
BUT if I try to save the object with deeper level data, it's not getting saved. I guess the update is not detected and hence the user didn't get replaced.
Example :
router.post('/user/addtask', auth ,async (req,res)=>{
const task = new Task({
name : req.body.name,
timing : new Date(),
state : false,
})
try {
const day = await req.user.days.find((day)=> day.day == req.body.day)
// day is found with no problem
req.user.days[req.user.days.indexOf(day)].tasks.push(task)
// console.log(req.user) returns exactly the expected results
await req.user.save(function(error,res){
console.log(res)
// console.log(res) returns exactly the expected results with the data filled
// and the tasks array is populated
// but on the database there is nothing
})
res.status(201).send(req.user)
} catch(e) {
res.status(400).send(e)
}
})
So I get the tasks array populated on the console even after the save callback but nothing on the db image showing empty tasks array
You're working on the user from the request, while you should first find the user from the DB like in your first example (User.findOne) and then update and save that model.
Use .lean() with your find queries whenever you are about to update the results returned by mongoose. Mongoose by default return instance objects which are immutable by nature. lean() method with find returns normal js objects which can be modified/updated.
eg. of using lean()
const user = await User.findOne({email : req.body.email}).lean();
You can read more about lean here
Hope this helps :)

How to update/insert an other document in cloud firestore on receiving a create event for a collection using functions

Let us assume that we have two collections say "users" and "usersList"
Upon creating a new user document in users collection with following object
{username: Suren, age:31}
The function should read the above data and update other collection i.e. "usersList" with the username alone like below
{username: Suren}
Let me know the possibility
The code I have tried is
exports.userCreated =
functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
return event.data.ref.set(post, {merge: true});
})
I have done it using below code
exports.userCreated = functions.firestore.document('users/{userId}')
.onCreate((event) => {
const firestore = admin.firestore()
return firestore.collection('usersList').doc('yourDocID').update({
name:'username',
}).then(() => {
// Document updated successfully.
console.log("Doc updated successfully");
});
})
If all you want to do is strip the age property from the document, you can do it like this:
exports.userCreated = functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
delete post.age;
return event.data.ref.set(post);
})

Resources