Update an array of objects mongoose - javascript

I know that this question might be beginner level but I haven't find anything yet.
I would like to update an array of objects with mongoose. I am interested in updating one object from the users array according to the index.
Usually one user is getting changed at a time.
Here is my schema:
_id: Schema.Types.ObjectId,
name: { type: String, required: true },
gm: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
users: [],
I want to update an object in the users array which is like this:
{
id:"5bcb7c7ff9c5c01b9482d244",
gm:"5bcb7c7ff9c5c01b9482d246",
name:"room 1"
users: [
{
id:"5bcb7c7ff9c5c01b9482d243",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
},
{
id:"5bcb7c7ff9c5c01b9482d241",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
]
}
I want to perform a patch or a post request to update one user each time from the user array. i am getting the id of the user from req.body to match it with my db.
My request is like this:
I would like to update based on a request like this:
data = {
stats={
power:"10",
vitality:"20"
}
}
Thanks in advance,
Cheers

You can do an update like this:
YourSchema.update({
'users.id': '5bcb7c7ff9c5c01b9482d243'
}, {
$set: {
'users.$.stats': data.stats
}
})
Which would update the first user with id 5bcb7c7ff9c5c01b9482d243 power stats to 20
This is using the update with the $ positional operator to update the element in the array.
Just have it set up in your post/patch request.

Related

Mongodb findOneAndUpdate - check if new document added, or just updated

I have the following code which is finding and updating an employee-store record (if it exists, otherwise it creates one).
I have lots of employees in different stores, and they can choose to change store at any point (as long as the location is in America)
Below is my code so far:
employee = await this.employeeStoreModel.findOneAndUpdate(
{ employee: employeeRecord._id, location: "America" },
{
employee: employeeRecord._id,
store: employeeRecord.store,
location: "America",
},
{ new: true, upsert: true }
);
This works correctly, however I am trying to return some messages from this based on what is being updated. It could be any of the following messages:
If it's a completely new employee-store record being added, then return "{{StoreID}} has a new employee - {{EmployeeID}}"
If it's a change of store on an existing employee-store record, then return "{{EmployeeID}} has changed from {{old StoreID}} to {{new StoreID}}"
Is this possible to do? Can anyone guide me on how I could start this?
In the Options field,
rawResult: true
You must add the parameter.
The result will be as in the example below.
{ response:
{ n: 1,
updatedExisting: false,
upserted: 5e6a9e5ec6e44398ae2ac16a },
value:
{ _id: 5e6a9e5ec6e44398ae2ac16a,
name: 'Will Riker',
__v: 0,
age: 29 },
ok: 1 }
Depending on whether there is an update or insert in the updatedExisting field, you can return any message you want.

MongoDB how to update a count field with the number of elements that are in an array on the document

I would like to update a field that I have on my Post model. The Post model looks like the following:
const postSchema = new Schema({
title: {
}
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User'}],
upvotes: {
type: Number,
default: 0
}
})
I have a database of posts that I would like to update by counting their likes (stored ID's) and update the upvotes to the number of likes in the document. Is there a way to do this? I can use the Mongo shell.
For Mongo version 4.2+ you can use pipelined updates to do this, like so:
db.collection.updateMany(
{},
[
{
$set: {
upvotes: {
$size: "$likes"
}
}
}
])
Mongo Playground
For lesser Mongo versions you'll have to read each document into memory, and execute an update for it.

Retrive some columns of relations in typeorm

I need to retrieve just some columns of relations in typeorm query.
I have an entity Environment that has an relation with Document, I want select environment with just url of document, how to do this in typeorm findOne/findAndCount methods?
To do that you have to use a querybuilder, here's an example:
return this.createQueryBuilder('environment') // use this if the query used inside of your entity's repository or getRepository(Environment)...
.select(["environment.id","environment.xx","environment.xx","document.url"])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getOne();
Sorry I can't add comment to post above. If you by not parsed data mean something like "environment.id" instead of "id"
try this:
return this.createQueryBuilder("environment")
.getRepository(Environment)
.select([
"environment.id AS id",
"environment.xx AS xx",
"document.url AS url",
])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getRawOne();
Here is the code that works for me, and it doesn't require using the QueryBuilder. I'm using the EntityManager approach, so assuming you have one of those from an existing DataSource, try this:
const environment = await this.entityManager.findOne(Environment, {
select: {
document: {
url: true,
}
},
relations: {
document: true
},
where: {
id: environmentId
},
});
Even though the Environment attributes are not specified in the select clause, my experience is that they are all returned in the results, along with document.url.
In one of the applications that I'm working on, I have the need to bring back attributes from doubled-nested relationships, and I've gotten that to work in a similar way, shown below.
Assuming an object model where an Episode has many CareTeamMembers, and each CareTeamMember has a User, something like the code below will fetch all episodes (all attributes) along with the first and last name of the associated Users:
const episodes = await this.entityManager.find(Episode, {
select: {
careTeamMembers: {
id: true, // Required for this to work
user: {
id: true,
firstName: true,
lastName: true,
},
}
},
relations: {
careTeamMembers: {
user: true,
}
},
where: {
deleted: false,
},
});
For some reason, I have to include at least one attribute from the CareTeamMembers entity itself (I'm using the id) for this approach to work.

Add to an array - sub-document without duplicate field values

I am trying to add an object to an array in MongoDB. I don't want it to be duplicated.
I am trying to update the user read array by using $addToset in findOneAndUpdate. However, it is inserting duplicate because of timestamp; the timestamp is an important property. I can't negate it. Can I insert based on key like userId? Please let me know.
{
_id: 'ddeecd8b-79b5-437d-9026-d0663b53ad8d',
message: 'hello world notification',
deliverToUsersList: [ '123-xxx-xx', '124-xxx-xx']
userRead: [
{
isOpened: true,
userId: '123-xxx-xx'
updatedOn: new Date(Date.now()).toISOString()
},
{
isOpened: true,
userId: '124-xxx-xx'
updatedOn: new Date(Date.now()).toISOString()
}
]
}
Add an index to the field userId and enable 'Avoid duplicates' in index settings.
I use Robo3T client to do that.
To add new objects without duplicate information into the userRead array, you have check for the duplicate information in the update method's query filter. For example, the following code will not allow adding new object with duplicate userId field value.
new_userId = "999-xxx-xx"
new_doc = { userId: new_userId, isOpened: true, updatedOn: ISODate() }
db.test_coll.findOneAndUpdate(
{ _id: 'ddeecd8b-79b5-437d-9026-d0663b53ad8d', "userRead.userId": { $ne: new_userId } },
{ $push: { "userRead" : new_doc } },
)

What is the best way to keep track of changes of a document's property in MongoDB?

I would like to know how to keep track of the values of a document in MongoDB.
It's a MongoDB Database with a Node and Express backend.
Say I have a document, which is part of the Patients collection.
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Burn fat"
}
Then I edit the "objective" property, so the document results like this:
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Gain muscle"
}
What's the best/most efficient way to keep track of that change? In other words, I would like to know that the "objective" property had the value "Burn fat" in the past, and access it in the future.
Thanks a lot!
Maintaining/tracking history in the same document is not all recommended. As the document size will keep on increasing leading to
probably if there are too many updates, 16mb document size limit
Performance degrades
Instead, you should maintain a separate collection for history. You might have use hibernates' Javers or envers for auditing for your relational databases. if not you can check how they work. A separate table (xyz_AUD) is maintained for each table (xyz). For each row (with primary key abc) in xyz table, there exist multiple rows in xyz_AUD table, where each row is version of that row.
Moreover, Javers also support MongoDB auditing. If you are using java you can directly use it. No need to write your own logic.
Refer - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/
One more thing, Javers Envers Hibernate are java libraries. But I'm sure for other programming languages also, similar libraries will be present.
There is a mongoose plugin as well -
https://www.npmjs.com/package/mongoose-audit (quite oudated 4 years)
https://github.com/nassor/mongoose-history#readme (better)
Maybe you can change the type of "objective" to array and track the changes in it. the last one of the array is the latest value.
Maintain it as a sub-document like below
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": {
obj1: "Gain muscle",
obj2: "Burn fat"
}
}
You can also maintain it as an array field but remember, mongodb doesn't allow you to maintain uniqueness in an array field and if you plan to index the "objective" field, you'll have to create a multi key index
I think the simplest solution would be to use and update an array:
const patientSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
objective: { type: String, required: true }
notes: [{
date: { type: Date, default: Date.now() },
note: { type: String, required: true }
}],
});
Then when you want to update the objective...
const updatePatientObjective = async (req, res) => {
try {
// check if _id and new objective exist in req.body
const { _id, objective, date } = req.body;
if (!_id || !objective) throw "Unable to update patient's objective.";
// make sure provided _id is valid
const existingPatient = await Patient.findOne({ _id });
if (!existingPatient) throw "Unable to locate that patient.";
// pull out objective as previousObjective
const { objective: previousObjective } = existingPatient;
// update patient's objective while pushing
// the previous objective into the notes sub document
await existingPatient.updateOne({
// update current objective
$set { objective },
// push an object with a date and note (previouseObjective)
// into a notes array
$push: {
notes: {
date,
note: previousObjective
},
},
}),
);
// send back response
res
.status(201)
.json({ message: "Successfully updated your objective!" });
} catch (err) {
return res.status(400).json({ err: err.toString() });
}
};
Document will look like:
firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
{
date: 2019-07-19T17:45:43-07:00,
note: "Gain muscle".
},
{
date: 2019-08-09T12:00:38-07:00,
note: "Work on cardio."
}
{
date: 2019-08-29T19:00:38-07:00,
note: "Become a fullstack web developer."
}
...etc
]
Alternatively, if you're worried about document size, then create a separate schema for patient history and reference the user's id (or just store the patient's _id as a string instead of referencing an ObjectId, whichever you prefer):
const patientHistorySchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
objective: { type: String, required: true }
});
Then create a new patient history document when the objective is updated...
PatientHistory.create({ _id, objective: previousObjective });
And if you need to access to the patient history documents...
PatientHistory.find({ _id });

Categories