Delete object from array by given _id in in Mongoose - node.js

In Mongoose model User given document look like this:
> db.users.find().pretty()
{
/* ... */
"events" : [
{
"_id" : ObjectId("537bb2faf87f9a552c3219ea"),
"message" : "Foo"
},
{
"_id" : ObjectId("537bb436c3b9a1aa2db71b7c"),
"message" : "Bar"
},
{
"_id" : ObjectId("537bb4ab2cc503e52e24d145"),
"message" : "Biz"
},
{
"_id" : ObjectId("537bb4d02cc503e52e24d146"),
"message" : "Pop"
}
]
}
Some function takes event _id as parameter and must delete object responding to this _id from MongoDB. I tried:
User
.findByIdAndUpdate(req.user._id, {
$pull: {events: {
$elemMatch: {_id: _eventId} //_eventId is string representation of event ID
}}
}, function(err) {
// ...
});
It is not working. What am I doing wrong?

Quote from SERVER-2016:
The argument to $pull is already applied to each element of the target
array, so using $elemMatch is redundant in the update context.
So just execute your query without $elemMatch:
User
.findByIdAndUpdate(req.user._id, {
$pull: {events: {
_id: _eventId //_eventId is string representation of event ID
}}
}, function(err) {
// ...
});

Related

Mongodb | Romove object from document's array

I tried to remove an object from document array but something not working:
This is my object:
{
"_id" : ObjectId("5b487aa1427e5a1edc1cdbac"),
"location" : {
"latitude" : 0,
"longitude" : 0
},
"email" : "ran#gmail.com",
"firstName" : "Ran",
"lastName" : "Alcobi",
"interests" : [
ObjectId("5b49a44bc3b19929b098547e")
],
}
I removed Interest from the interests collection and I need that the interest will be removed from the Interests array of the user:
this is my code:
router.delete('/:id', (req, res) => {
var id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
Interest.findOneAndRemove({
_id: id,
}).then((interest) => {
if (!interest) {
return res.status(404).send();
}
User.update(
{$pull:
{interests: {$in : interest._id} }
},
{multi: true},
).then((user) => {
console.log(user);
res.send({interest});
}).catch((e) => {
res.status(400).send();
});
});
});
Thanks a lot for helpers. I would be happy to know what is my mistake.
You are passing $pull in wrong parameter of update query... In update query first parameter is for filter and second parameter is for update operation... So you have to first filter with $in and then $pull from the interest array...
So, finally you need to do something like this
User.update(
{ interests: { $in : [interest._id] } },
{ $pull: { interests: interest._id } }
{ multi: true },
)

Query nested array

I have the below User document. I want to return a list of all 'friends' where friends.name is equal to "Bob".
{
"_id" : ObjectId("5a4be9f200471a49d2e23ce4"),
"name": "James"
"friends" : [
{
"_id" : ObjectId("5a4be9f200471a49d2e23ce6"),
"dob" : ISODate("2018-01-02T00:00:00.000Z"),
"name" : "Bob"
},
{
"_id" : ObjectId("5a4be9f200471a49d2e23ce5"),
"dob" : ISODate("2018-01-02T00:00:00.000Z"),
"name" : "Fred"
}
],
"__v" : 0
}
When I try to query using the below, its working but its returning the whole friends list, not just Bob.
User.findOne({ "friends.name": "Bob" }, function(err, friends) {
if(err) return next(err);
res.send(friends);
});
How can I query so I only to return Bob object and not Fred?
Your query is correct, but it returns all user documents having at least one friend matching your condition.
If you just want matching items from the friends collection, you might do something like this.
db.User.aggregate([
{ $unwind: "$friends" },
{ $replaceRoot: { newRoot: "$friends" } },
{ $match: { name: "Bob" }}
])

Sort Embedded Documents in Array and fetch specifics

I have a mongoose model like this:
var activityItem = mongoose.Schema({
timestampValue: Number,
xabc: String,
full: Boolean,
comp: Boolean
});
var ABC = mongoose.Schema({
activity: [activityItem],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
username: String
});
I want to get the activityItem array elements that have a timestampValue less than a specific value. Also, I want to sort the activity array first according to the timestampValue
This is the code that I currently have. And it doesn't work.
UserActivity.findOne({
'user': current_user,
'activity' : {
$all: [
{
"$elemMatch": {
timestampValue: {
$lte: time
}
}
}
]
}
},
function(err, user){
})
Sample Document structure:
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [],
"completed" : [],
"activity" : [
{
"timestampValue": 1456902600000,
"xabc": "Some value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d2")
},
{
"timestampValue": 1456702600000,
"xabc": "Some other value",
"full": true,
"comp": false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
],
"__v" : 1
}
The POST call has the following params
hash: "2e74aaaf42aa5ea733be963cb61fc5ff"
time: 1457202600000
hash comes into the picture once i have the docs from mongo
time is a unix timestamp value.
Instead of returning only the elements that are less than the time value, it is returning all the array elements. I tried the aggregation framework to sort the array before querying, but couldn't get the hang of it.
Any help would be greatly appreciated.
Please try to do it through aggregation as below
ABS.aggregate([
// filter the document by current_user
{$match: {user: ObjectId(current_user)}},
// unwind the activity array
{$unwind: '$activity'},
// filter the timestampValue less than time
{$match: {'activity.timestampValue': {$lte: time}}},
// sort activity by timestampValue in ascending order
{$sort: {'activity.timestampValue': 1}},
// group by _id, and assemble the activity array.
{$group: {_id: '$_id', user: {$first: '$user'},activity: {$push: '$activity'}}}
], function(err, results){
if (err)
throw err;
// populate user to get details of user information if needed
//ABS.populate( results, { "path": "user" }, function(err, rets) {
//
//});
});
Well, it seems little bit tricky with MongoDb aggregation pipeline unless you have MongoDB 3.2, but you can definitely
achieve your result with help of map-reduce.
e.g.
MongoDB version < 3.2
var findActivities = function (time) {
db.col.mapReduce(function () {
var item = Object.assign({}, this);
delete item.activity;
item.activity = [];
for (var i = 0; i < this.activity.length; i++) {
if (this.activity[i].timestampValue <= time) {
item.activity.push(this.activity[i]);
}
}
emit(item._id, item);
}, function (k, v) {
return {items: v};
}, {
out: {"inline": true},
scope: {time: time}
}).results.forEach(function (o) {
printjson(o); // Or perform action as appropriate
});
};
Based your sample data when called findActivities(1456802600000), it will find and return only those documents matching criteria.
{
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"value" : {
"_id" : ObjectId("56d5e88adfd14baf1848a7c6"),
"user" : ObjectId("56bf225342e662f4277ded73"),
"notifications" : [
],
"completed" : [
],
"__v" : NumberInt(1),
"activity" : [
{
"timestampValue" : NumberLong(1456702600000),
"xabc" : "Some other value",
"full" : true,
"comp" : false,
"_id" : ObjectId("56d5e88adfd14baf1848a7d3")
}
]
}
}
MongoDB version 3.2+
db.col.aggregate([
{$project:{user:1, notifications:1, completed:1, activity:{
$filter:{input: "$activity", as: "activity", cond:{
$lte: ["$$activity.timestampValue", 1456802600000]}}}}}
])
Both solutions will have same output.

Category hierarchy aggregation using mongodb and nodejs

My document structure is as follows:
{
"_id" : ObjectId("54d81827e4a4449d023b4e34"),
"cat_id" : 1,
"description" : "Refridgerator",
"image" : "refridgerator",
"parent" : null,
"slug" : "refridgerator"
}
{
"_id" : ObjectId("54dc38bce4a4449d023b4e58"),
"name" : "Ice Cream",
"description" : "Ice Cream",
"image" : "ice-cream.jpg",
"slug" : "ice-cream",
"parent" : "54d81827e4a4449d023b4e34"
}
{
"_id" : ObjectId("54dc3705e4a4449d023b4e56"),
"name" : "Chocolate",
"description" : "Chocolate",
"image" : "chocolate.jpg",
"slug" : "chocolate",
"parent" : "54d81827e4a4449d023b4e34"
}
I’m making a category hierarchy using mongodb and nodejs.
Now I wish to query for _id = ‘54d81827e4a4449d023b4e34’ (Refridgerator) and should get back all the child categories
How to achieve the above in nodejs?
Also, nodejs uses async call to the database, I’m unable to get the json structured with parent – child relations.
How would I do the async call for this?
You want the refridgerator and all the subcategories?
And async is also a problem?
I think you can use aggregation here.
Say you're looking for a category with _id variable which is an ObjectId of what you want, and it's subcategories.
db.yourCollection.aggregate({
// get stuff where you have the parent or subcats.
$match: {
$or: [
{_id: ObjectId("54de8b9f022ff38bbf5e0530")},
{parent: ObjectId("54de8b9f022ff38bbf5e0530")}
]
}
},
// reshape the data you'll need further on from each mached doc
{
$project: {
_id: false,
data: {
id: '$_id',
name: '$name'
// I guess you'll also want the `slug` and `image` here.
// but that's homework :)
},
parent: '$parent'
}
},
// now put a common _id so you can group them, and also put stuff into arrays
{
$project: {
id: {$literal: 'id'},
mainCategory: {
// if our parent is null, put our data.
// otherwise put null here.
$cond: [{$eq: [null, '$parent']}, {_id: '$data.id', name: '$data.name'}, undefined]
},
subcat: {
// here is the other way around.
$cond: [{$ne: [null, '$parent']}, {_id: '$data.id', name: '$data.name'}, null]
}
}
// that stage produces for each doc either a mainCat or subcat
// (and the other prop equals to null)
},
// finally, group the things so you can have them together
{
$group: {
_id: '$id',
// a bit hacky, but mongo will yield to it
mainCategory: {$max: '$mainCategory'},
subCategories: {
// this will, unfortunately, also add the `null` we have
// assigned to main category up there
$addToSet: '$subcat'
}
}
},
// so we get rid of the unwanted _id = 'id' and the null from subcats.
{
$project: {
_id: false,
mainCategory: 1,
subCategories: {
$setDifference: ['$subCategories', [null]]
}
}
})
Given this data set:
[{
"_id" : ObjectId("54de8b9f022ff38bbf5e0530"),
"name" : "Fridge",
"parent" : null
},
{
"_id" : ObjectId("54de8bba022ff38bbf5e0531"),
"name" : "choco",
"parent" : ObjectId("54de8b9f022ff38bbf5e0530")
},
{
"_id" : ObjectId("54de8bc8022ff38bbf5e0532"),
"name" : "apple",
"parent" : ObjectId("54de8b9f022ff38bbf5e0530")
}
I get this result:
{
"result" : [
{
"mainCategory" : {
"_id" : ObjectId("54de8b9f022ff38bbf5e0530"),
"name" : "Fridge"
},
"subCategories" : [
{
"_id" : ObjectId("54de8bc8022ff38bbf5e0532"),
"name" : "apple"
},
{
"_id" : ObjectId("54de8bba022ff38bbf5e0531"),
"name" : "choco"
}
]
}
],
"ok" : 1
}
As for async, typically you'd do something like this:
db.collection.aggregate(thePipeLineAbove, function(err, results) {
// handle err
if (err) {
// deal with it
} else {
console.log(results);
}
});
But that depends a bit on your MongoDB driver.
You could expand this even if you have deeper hierarchy structure.
This has nothing to do with NodeJS, it's your data structure that matters.
refer to my answer to this question, the first part is about how to implement it efficiently.

How to build query for relating parent document's field to embedded array document's field. Using MongoDB aggregation Operators

Consider the following document in the collection named 'CityAssociation'
{
"_id" : "MY_ID",
"ThisCityID" : "001",
"CityIDs" : [{
"CityID" : "001",
"CityName" : "Bangalore"
}, {
"CityID" : "002",
"CityName" : "Mysore"
}],
"CityUserDetails": {
"User" : "ABCD"
}
}
Now I have User value i.e. in above case I have value ABCD and want to find it with only city where the first level's field ThisCityID matches to the embedded array documnet's field CityID. Finally I need to project as follows (for the above case):
{
'UserName': 'ABCD',
'HomeTown':'Bangalore'
}
In Node.js + MongoDB native drive, I wrote a aggregation query as follows which is not working as expected.
collection.aggregate([
{ $match: { 'CityUserDetails.User': 'ABCD', 'CityIDs': { $elemMatch: { CityID: ThisCityID}}} },
{ $unwind: "$CityIDs" },
{ $group: {
_id: '$_id',
CityUserDetails: { $first: "$CityUserDetails" },
CityIDs: { $first: "$CityIDs" }
}
},
{ $project: {
_id: 0,
"UserName": "$CityUserDetails.User",
"HomeTown": "$CityIDs.CityName"
}
}
], function (err, doc) {
if (err) return console.error(err);
console.dir(doc);
}
);
Can anyone tell me how this can be done with query.
Note: On MongoDB schema we don't have control to change it.
You can use the $eq operator to check if the first level's field ThisCityID matches embedded array document's field CityID.
db.city.aggregate([
{ $match : { "CityUserDetails.User" : "ABCD" }},
{ $unwind : "$CityIDs" },
{ $project : {
matches : { $eq: ["$CityIDs.CityID","$ThisCityID"]},
UserName : "$CityUserDetails.User",
HomeTown : "$CityIDs.CityName"
}},
{ $match : { matches : true }},
{ $project : {
_id : 0,
UserName : 1,
HomeTown : 1
}},
])
And the result is:
{
"result" : [
{
"UserName" : "ABCD",
"HomeTown" : "Bangalore"
}
],
"ok" : 1
}

Resources