Mongoose: Adding Attributes to Nested Objects - javascript

I'm currently using AngularJS, and the backend is using NodeJS and Express. I use Mongoose to access the database. I'm trying to figure how to add attributes to nested objects and I can't for the life of me find out how to do it anywhere on the web.
My Schema looks like this:
{
id: {
type: String
},
involved: {
type: String
},
lastMsgRead: Object
}
lastMsgRead will look something like this:
{
user1: "somestringblahblah",
user2: "someotherstring",
}
and so on.
My question is, how would I update lastMsgRead with Mongoose to add another attribute to it, such as adding user3 so it now looks like:
{
user1: "somestringblahblah",
user2: "someotherstring",
user3: "anotherstring"
}
The entire document would like this after the update:
{
id: "string",
involved: "string",
lastMsgRead: {
user1: "somestringblahblah",
user2: "someotherstring",
user3: "anotherstring"
}
}
Edit:
After I add the attribute, how would I then update it in the future?

You can use .dot notation to update in nested object
db.collection.update(
{ },
{ "$set": { "lastMsgRead.user3": "anotherstring" } }
)

in mongoose 5.1.0 and up you can approach this with the MongooseMap.
You can define it in the schema as followed:
{
id: {
type: String
},
involved: {
type: String
},
lastMsgRead: {
type: Map,
of: String
}
}
you can then add a new value by .set(key, value)
myDoc.lastMsgRead.set(key, value)
and get a value by .get(key)
myDoc.lastMsgRead.get(key)

Related

How to filter only some rows of an array inside a document?

I have a MongoDB collection of documents, using a schema like this:
var schema = new Schema({
name: String,
images: [{
uri: string,
active: Boolean
}]
});
I'd like to get all documents (or filter using some criteria), but retrieve - in the images array - only the items with a specific property (in my case, it's {active: true}).
This is what I do now:
db.people.find( { 'images.active': true } )
But this retrieves only documents with at least one image which is active, which is not what I need.
I know of course I can filter in code after the find is returned, but I do not like wasting memory.
Is there a way I can filter array items in a document using mongoose?
Here is the aggregation you're looking for:
db.collection.aggregate([
{
$match: {}
},
{
$project: {
name: true,
images: {
$filter: {
input: "$images",
as: "images",
cond: {
$eq: [
"$$images.active",
true
]
}
}
}
}
}
])
https://mongoplayground.net/p/t_VxjfiBBMK

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.

MongoDB/Mongoose - Adding an object to an array of objects only if a certain field is unique

So I have a nested array of objects in my MongoDB document and I would like to add a new object to the array only if a certain field (in this case, eventId) is unique. My question is very similar to this post, only I cannot seem to get that solution to work in my case.
Here is what the documents (UserModel) look like:
{
"portal" : {
"events" : [
{
"important" : false,
"completed" : false,
"_id" : ObjectId("5c0c2a93bb49c91ef8de0b21"),
"eventId" : "5bec4a7361853025400ee9e9",
"user_notes" : "My event note"
},
...and so on
]
}
}
And here is my (so far unsuccessful) Mongoose operation:
UserModel.findByIdAndUpdate(
userId,
{ "portal.events.eventId": { $ne: req.body.eventId } },
{ $addToSet: { "portal.events": req.body } },
{ new: true }
);
Basically I am trying to use '$ne' to check if the field is unique, and then '$addToSet' (or '$push', I believe they are functionally equivalent in this case) to add the new object.
Could anyone point me in the right direction?
Cheers,
Gabe
If you look into the documentation on your method you will see that the parameters passed are not in the proper order.
findByIdAndUpdate(id, update, options, callback)
I would use update instead and have your id and portal.events.eventId": { $ne: req.body.eventId } part of the initial filter followed by $addToSet: { "portal.events": req.body }
Something among these lines:
UserModel.update(
{
"_id": mongoose.Types.ObjectId(userId),
"portal.events.eventId": { $ne: req.body.eventId }
},
{ $addToSet: { "portal.events": req.body } },
{ new: true }
);
You need to include your eventId check into condition part of your query. Because you're usig findByIdAndUpdate you can only pass single value matched against _id as a condition. Therefore you have to use findOneAndUpdate to specify custom filtering condition, try:
UserModel.findOneAndUpdate(
{ _id: userId, "portal.events.eventId": { $ne: req.body.eventId } },
{ $addToSet: { "portal.events": req.body } },
{ new: true }
);

Update an array of objects mongoose

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.

Extract Decimal from NumberDecimal with Mongo(ose)

In the Numeric Model for storing monetary values, the MongoDB docs state:
From the mongo shell decimal values are assigned and queried using the NumberDecimal() constructor.
Similarly, when using the Morphia Java library, BigDecimals are automatically inserted as BigDecimals.
I'm querying Mongo in Node with Mongoose and attempting to extract the numeric value of a field stored as a NumberDecimal. However, the value is oddly wrapped in query results and I'm not sure how to extract it through Mongo or Mongoose:
[
{
"openValue":{
"$numberDecimal":"119.931"
},
"timestamp":"2017-01-20T10:30:00.000Z"
},
{
"openValue":{
"$numberDecimal":"119.965"
},
"timestamp":"2017-01-20T10:31:00.000Z"
}
]
One post I read stated using parseFloat() in my application code will perform what I desire, however it's not efficient to iterate through the result to perform this transformation. Avoiding iterating and transforming would mean running the function on the NumberDecimals whenever I want their value every time, which would be annoying.
Is there a way I can use Mongo or Mongoose to convert the above JSON query-result into what's below?
[
{
"openValue": 119.931,
"timestamp":"2017-01-20T10:30:00.000Z"
},
{
"openValue": 119.965,
"timestamp":"2017-01-20T10:31:00.000Z"
},
{
"openValue": 119.975,
"timestamp":"2017-01-20T10:32:00.000Z"
}
]
I tried selecting the field as ...openValue.$numberDecimal, but this didn't work. Thank you!
Edit: Here's my Mongoose schema:
var EquityHistoryModel = new Schema({
_id: {
equityIdentifier: { type: {
exchange: { type: String, index: true },
symbol: { type: String, index: true }
}, index: true },
instant: { type: Date, index: true },
durationMinutes: { type: Number }
},
open: { type: mongoose.Schema.Types.Decimal },
high: { type: mongoose.Schema.Types.Decimal },
low: { type: mongoose.Schema.Types.Decimal },
close: { type: mongoose.Schema.Types.Decimal },
volume: { type: Number },
isDividendAdjusted: { type: Boolean },
isSplitAdjusted: { type: Boolean }
}, { collection: 'equityHistories', versionKey: false });
Here's the Mongoose query for the first JSON result above:
mongo.EquityHistoryModel.aggregate([
{
"$match":{
"_id.equityIdentifier.exchange":passed_in,
"_id.equityIdentifier.symbol":passed_in,
"_id.instant":passed_in
}
},
{
"$project":{
"_id":0,
"openValue":"$open",
"timestamp":"$_id.instant"
}
}
],
You can also overwrite the toJSON method:
// Never return '__v' field and return the 'price' as String in the JSON representation
// Note that this doesn't effect `toObject`
EquityHistoryModel.set('toJSON', {
getters: true,
transform: (doc, ret) => {
if (ret.price) {
ret.price = ret.price.toString();
}
delete ret.__v;
return ret;
},
});
Contrary to what I expected, it seems the values are automatically extracted. Stringifying the result automatically wraps the value in the NumberDecimal. See the code with the output manually placed below:
console.log(docs[0].openValue);
119.800
console.log(JSON.stringify(docs[0].openValue]);
{"$numberDecimal":"119.800"}
Also, I struggled with sending query results, due to res.json or res.send using stringify. Instead I wrote a replace function and set the property in Node.

Categories