Mongoosejs refresh a document - javascript

Suppose I have a document for example: var doc = Model.findOne({name:"name"});
Now if the document gets edited trough another connection the the database, doc doesn't hold the right information. I do need it, so I have to "refresh" or "redownload" it from the database. Is there any way to do this with only the object "doc"?

Assuming doc contains the document instance to refresh, you can do this to generically refresh it:
doc.model(doc.constructor.modelName).findOne({_id: doc._id},
function(err, newDoc) {
if (!err) {
doc = newDoc;
}
}
);
However, it's better to not persist/cache Mongoose document instances beyond your immediate need for them. Cache the immutable _id of docs you need to quickly access, not the docs themselves.

Sorry I know this is old but I have a better solution in case anyone else is still looking around.
What you can do in this situation of concurrent access to the same document is perform a normal save.
Mongoose uses the __v property of an object to make sure old instances don't overwrite the newest ones.
So if you have 2 instances of doc.__v = 0, and A saves first, the db now has doc.__v = 1. So that when B saves, it'll error and because B.__v = 0, not 1. So what you can do is then catch the error (it should be a specific mongoose error) and handle it by refreshing the object, (which should bump it up to __v = 1) then either SAVE it or not SAVE it depending on what you want to do.
so in code
doc.save(function (err, _doc) {
if (err) {
if (err.some_error_property_telling_you_version_was_wrong) {
// Do your business here
// Refresh your object
// Model.findById(doc._id, function (err, _refreshed_doc) {});
// doc = _refreshed_doc;
// doc.set('property', value); set the value again
// Then re-save? or maybe make this a recursive function,
// so it can keep trying to save the object?
} else {
// Handle it like a normal error
}
}
});

If it's defined with let you can just do
await thing.update(stuff);
thing = await Thing.findById(thing._id);
or if it was with const then do it immutably:
await thing.update(stuff);
const updatedThing = await Thing.findById(thing._id);

Related

Firebase Firestore: How to update or access and update a field value, in a map, in an array, in a document, that is in a collection

Sorry for the long title. Visually and more precise, I would like to update the stock value after a payment is made. However, I get stuck after querying the entire document (e.g. the selected one with title sneakers). Is there a way to actually query and update for example the Timberlands stock value to its value -1. Or do you have to get all data from the entire document. Then modify the desired part in javascript and update the entire document?
Here is a little snippet of a solution I came up with so far. However, this approach hurts my soul as it seems very inefficient.
const updateFirebaseStock = (orders) => {
orders.forEach( async (order) => {
try {
collRef = db.doc(`collections/${order.collectionid}`);
doc = await collRef.get();
data = doc.data();
//Here:const newItems = data.items.map(if it's corr name, update value, else just return object), results in desired new Array of objects.
//Then Update entire document by collRef.update({items: newItems})
} catch (error) {
console.error(error)
};
});
}
You don't need to get the document at all for that, all you have to do is use FieldValue.increment(), using your code as a starting point it could look like this:
collRef = db.doc(`collections/${order.collectionid}`);
collRef.update({
Price: firebase.firestore.FieldValue.increment(-1)
});
You can increment/decrement with any numeric value using that function.

How to check if a container exists in cosmos DB using the node sdk?

I want to check if a container exists and if not, initialize it. I was hoping for something like the following:
const { endpoint, key, databaseId } = config;
const containerName = "container1"
const client = new CosmosClient({ endpoint ,key});
const containerDefinition = getContainerDefinition(containerName);
const db = await createDatabase(client, databaseId);
if (!db.containers.contains(containerName)
{
// Do something
}
The reason I'm not using "createIfNotExists" is because I would need to make a 2nd call to check if the container returned is populated with items or not. The container I'm creating is going to hold settings data which will be static once the container is initially created. This settings check is going to happen per request so I'd like to minimize the database calls and operations if possible.
I tried doing something like:
try
{
db.container(containerName).read();
}
catch(err)
{
if(err.message.contains("Resource Not Found"))
{
// do something
}
}
But that doesn't seem like the right way to do it.
Any help would be appreciated!
I'm not quite clear on why you would need to do this since typically you only need to do this sort of thing once for the life of your application instance. But I would not recommend doing it this way.
When you query Cosmos to test the existence of a database, container, etc., this hits the master partition for the account. The master partition is kind of like a tiny Cosmos database with all of your account meta data in it.
This master partition is allocated a small amount of the RU/s that manage the metadata operations. So if you app is designed to make these types of calls for every single request, it's quite likely you will get rate limited in your application.
If there is some way you can design this such that it doesn't have to query for the existence of a container then I would pursue that instead.
Interesting question. So i think you have few options
Just call const { container } = await database.containers.createIfNotExists({ id: "Container" }); it will be fast probably few milliseconds, since I went via code at looks like it will always try to read from cosmos :( If you want to still check if container exists sdk has methods(But again no real benefits ):
const iterator = database.containers.readAll();
const { resources: containersList } = await iterator.fetchAll();
Create singleton and first time just initialise all your containers so next time you dont call it, sure if you scale each instance will do the same
My favourite, use terraform/armtemplates/bicep to spin up infrastructure so you code wont need to handle that
You can try this code:
async function check_container_exist(databaseId,containerId) {
let exist = false;
const querySpec = {
query: "SELECT * FROM root r WHERE r.id = #container",
parameters: [
{name: "#container", value: containerId}
]
};
const response = await client.database(databaseId).containers.query(querySpec).fetchNext();
if(response.resources[0]){
exist = true;
}
return exist;
}

Mongoose redis caching

https://medium.com/#haimrait/how-to-add-a-redis-cache-layer-to-mongoose-in-node-js-a9729181ad69
In this guide. So I mostly do queries like
{
id: <guild id>
}
so whenever new document is created.
const book = new Book({
title,
content,
author
});
try {
await book.save();
clearKey(Book.collection.collectionName);
res.send(book);
} catch (err) {
res.send(400, err);
}
will it remove stuff from caches if i use {id: } or will it delete only data on cache that is like empty object or like Model#find()?
I also have another problem which is not related to that but could ask.
Imagine I do this
const result = Model.findOne()
Cache.set(<anything>, JSON.stringify(result));
const cached = Cache.get(<anything>)
const result = new Model(cached);
result.message++;
await result.save().catch(console.error)
it throws the MongoError: E11000 duplicate key error collection:
How to fix that?
clearKey(Book.collection.collectionName) In short, it will clear all the cache for the collection.
TLDR
In your case this.hashKey = JSON.stringify(options.key || this.mongooseCollection.name); is collectionName
https://redis.io/commands/hget
Returns the value associated with field in the hash stored at key.
clearKey(hashKey) {
client.del(JSON.stringify(hashKey));
}
https://redis.io/commands/del
Removes the specified keys. A key is ignored if it does not exist.
So when you call clearKey(Book.collection.collectionName); it calls client.del which will delete all the records for that particular collection. as the complete hash is deleted.
To delete specific fields not the full hash :-
https://redis.io/commands/HDEL
Removes the specified fields from the hash stored at key. Specified fields that do not exist within this hash are ignored. If key does not exist, it is treated as an empty hash and this command returns 0.
You can use my library, which has event-based logic to clear cache if some related changes appear. It also has an option to pass a custom callback to check, if your record was marked as deleted, but not physically removed from database.
https://www.npmjs.com/package/speedgoose

MongoDB auto updates between posts and comments vice versa

I am building a web application, and I am spending so long time to take care of updates between related documents.
For example, I have 'Task' document and 'User' document. When task is made, multiple users will be assigned to it. Thus,
taskA.assigned = ["1321231fsdfsdf"(userA's _id), "12312313asdasdasd"(userB's _id)]
userA.tasks = [..., "1231321"(taskA's _id),...]
userB.tasks = [..., "12313211"(taskB's _id),...]
I could handle it well when it comes to just creating tasks. However, it becomes too tricky when I am going to edit tasks. If user B is deleted from taskA, I have to delete userB's id and go to the userB's tasks property and delete taskA's id too.
Is there any shortcut and automatic way to deal with it? Thank you for your time to read it. Let me know if I was too vague, I will add more detail.
In a relational database like MySQL using foreign keys and cascade updates could be done automatically, but in MongoDB that's not possible.
But I see in the tags you are using Moongose, so using a post save hook could do the trick. You can set a hook that updates automatically the user collection each time a task is updated, or viceversa.
Other option would be changing your data estructure, but this depends on your case, there are some facts to take into account. I think we don't have enough information to judge, but there are many resources speaking about data normalization in MongoDB, you can check for example the official MongoDB manual.
I found a way to do this easily and with less lines of codes. Around 100 lines reduced to around 30 lines by doing this.
Long story short, I used 'update()' method and various mongo operators, such as $in, $push, or $pull.
Here is my final codes that are optimized with use of update method.
var edit = req.body;
edit.assignedTo = edit.assignedTo.split(',');
var old = req.task;
var idQueries = edit.assignedTo.map(function (x) {
return mongoose.Types.ObjectId(x);
});
User.update({tasks: old._id}, {$pull: {tasks: old._id}}, {multi: true}, function () {
// Update to remove original task's id from users assigned to it.
User.update({_id: {$in: idQueries}}, {$push: {tasks: old._id}}, {multi: true}, function () {
// Update to add edited tasks'id to new users assigned to it.
old.lastAction = 'edited';
old.edited = true;
old.editedAt = Date.now();
old.titke = edit.title;
old.desc = edit.desc;
old.dueBy = edit.dueBy;
old.assignedTo = edit.assignedTo;
old.save(function (err, task) {
if (err) return next(err);
User.populate(task, 'assignedTo', function (err, task) {
res.json(task);
});
});
});
});
Wish this help some people!

Add a new field to a document mongodb

I am very new to mongodb and have a basic question that I am having trouble with. How do I get the ID field of a document that has already been created? I need the ID so i can update/add a new field to the document.
//newProfile is an object, one string it holds is called school
if(Schools.find({name: newProfile.school}).fetch().length != 1){
var school = {
name: newProfile.school
}
Meteor.call('newSchool', school);
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
//Method 2?
//Schools.update(_id: <what goes here?>, {$push: {enrolledStudents: Meteor.user()}});
}
else {
//Schools.update... <add users to an existing school>
}
I create a new school document if the listed school does not already exist. Schools need to hold an array/list of students (this is where i am having trouble). How do I add students to a NEW field (called enrolledStudents)?
Thanks!
I'm having some trouble understanding exactly what you're trying to do. Here's my analysis and understanding so far with a couple pointers thrown in:
if(Schools.find({name: newProfile.school}).fetch().length != 1){
this would be more efficient
if(Schools.find({name: new Profile.school}).count() != 1) {
Meteor.call('newSchool', school);
Not sure what you're doing here, unless you this will run asynchronously, meaning by the time the rest of this block of code has executed, chances are this Meteor.call() function has not completed on the server side.
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
Judging by the if statement at the top of your code, there is more than one school with this name in the database. So I'm unsure if the schoolDoc variable is the record you're after.
I believe you are having trouble because of the asynchronous nature of Meteor.call on the client.
Try doing something like this:
// include on both server and client
Meteor.methods({
newSchool: function (school) {
var newSchoolId,
currentUser = Meteor.user();
if (!currentUser) throw new Meteor.Error(403, 'Access denied');
// add some check here using the Meteor check/match function to ensure 'school'
// contains proper data
try {
school.enrolledStudents = [currentUser._id];
newSchoolId = Schools.insert(school);
return newSchoolId;
} catch (ex) {
// handle appropriately
}
}
});
// on client
var schoolExists = false;
if (Schools.findOne({name: newProfile.school})) {
schoolExists = true;
}
if (schoolExists) {
var school = {
name: newProfile.school
};
Meteor.call('newSchool', school, function (err, result) {
if (err) {
alert('An error occurred...');
} else {
// result is now the _id of the newly inserted record
}
})
} else {
}
Including the method on both the client and the server allows Meteor to do latency compensation and 'simulate' the insert immediately on the client without waiting for the server round-trip. But you could also just keep the method on the server-side.
You should do the enrolledStudents part on the server to prevent malicious users from messing with your data. Also, you probably don't want to actually be storing the entire user object in the enrolledStudents array, just the user _id.
For what you're trying to do, there is no need to get the _id. When you use update, just switch out the {_id: schoolDoc._id} with your query. Looks like using {name: newProfile.school} will work, assuming that the rest of your code does what you want it to do.
While that would work with the normal Mongo driver, I see that Meteor does not allow your update query to be anything but _id: Meteor throws throwIfSelectorIsNotId exception
First, make sure that you're pulling the right document, and you can try something like this:
var school_id = Schools.findOne({name: newProfile.school})._id;
Schools.update({_id: school_id}, { $push: { enrolledStudents: Meteor.user()}});
If that doesn't work, you'll have to do a little debugging to see what in particular about it isn't working.

Categories

Resources