Mongodb: Find ids $in nested array property - javascript

I have the following data structure for my users in my mongodb:
{
_id: "someId",
profile: {
username: "oliv",
friendRequests: [
{ fromUserId: "anId", accepted: false, created: "someDate"},
{ fromUserId: "otherId", accepted: true, created: "otherDate"}
]
}
I'd like to retrieve the user objects that are referenced in my logged user's friendsRequested.
So I tried something like this:
Meteor.users.find({_id: {$in: Meteor.user().profile.friendRequests.fromUserId}});
// FYI: Meteor.users is the collection and Meteor.user() retrieves the current user
But it's not working. I'm assuming it's because of the nested array.
Is there any way of telling mongo to iterate through the fromUserId or something?
Thanks

Change your query to:
Meteor.users.find({_id: {$in: _.pluck(Meteor.user().profile.friendRequests, 'fromUserId') }});
Reasoning:
friendRequests is an array of objects, and $in wants an array of strings(the ids), using _.pluck you're able to pass an array of objects, and tell _ to only return the field fromUserId for each object in the array.
Question in comment ("what if I only want to get the ids from friend requests where "accepted" is false?"):
_.pluck(
_.filter(Meteor.user().profile.friendRequests, function (req) {
return !req.accepted;
}),
'fromUserid'
);
Underscore filter docs

Related

MongoDB: return just the document from an array that matches a certain query (JS)

I have a MongoDB collection in the form of:
users: {
{
user_id: 1000,
activities: [
{id: 1, activity: 'swimming'},
{id: 2, activity: 'running'},
{id: 3, activity: 'biking'},...
]
},...
}
and I want to get the activity document that matches a specific ID. For example, if I query using {id: 1}, I want an output of {id: 1, activity: 'swimming'}. Currenlty, I'm trying to use findOne({activities: {$elemMatch: {id: 1}}}), but it returns the entire user document (the user id, and all the activities).
The code I'm using is:
id = req.body.queryID;
db.collection('users').findOne({activities: {$elemMatch: {id}}})
.then((document) => {
console.log(document);
// more code after this
});
I've also tried to query using aggregate() and findOne({}, {activities: {$elemMatch: {id}}}), but I haven't been able to figure it out.
I am on MongoDB version 4.4.8 according to db.version(). Any help is appreciated!
Your attempts look close, I think you just need to put them together. findOne takes two optional arguments, a query and a projection. The query tells MongoDB what document to look for and return. The projection document tells MongoDB what field(s) to include in the document returned by the query.
What you have here:
db.collection('users').findOne({ activities: { $elemMatch: { id }}})
Passes one argument, the query. It will look for a document where there is one element in the activities array with an id equal to the value of the variable id. You could also write this as:
db.collection('users').findOne({ "activities.id": id })
You'd also like to only return documents in the activities array with a matching ID. This is when you'd do something like your other attempt, where $elemMatch is in the second argument, the projection:
db.collection('users').findOne({}, {activities: {$elemMatch: {id}}})
But because you passed an empty query, MongoDB will return any document, not necessarily one that contains an activity with a matching ID.
So if you pass both a query and the projection, I think you should get what you're looking for. It might look like this:
db.collection('users').findOne({ "activities.id": id }, { activities: { $elemMatch: { id }}})
This will include only the _id field and the matching activities document if one exists. If you want to include other fields of the document, they need to be explicitly included. See this documentation about projection.
You can use the aggregate method with the unwind, match and project pipelines
db.users.aggregate([
{$unwind: "$activities"},
{$match: {"activities.id": id}},
{$project: {id: "$activities.id", activity: "$activities.activity", _id: 0}}
])
NOTE: This code is written using the mongo shell syntax.
The query does the following
starts with the unwind pipeline which first pulls out all the items in the activities array.
The match pipeline finds the array element that has the id we're looking for
The project pipeline returns the output as desired
Hope this helps
Please try as follow:
db.users.aggregate([
{
'$unwind': '$activities'
}, {
'$match': {
'id': id
}
}, {
'$project': {
'activity': '$activities.activity',
'id': '$activities.id',
'_id':0
}
}
]);

Mongoose: find document by values inside an array field

I've got a chat schema that looks like that:
var chatSchema = new mongoose.Schema({
users: [{
type: mongoose.Schema.Types.ObjectId,
required: true
}]
});
It contains array of user IDs.
Now I want to find one chat document that contains an array of two user IDs.
At the beginning I tried to do this:
Chat.findOne({ users: { $in: [req.user_id, receiver._id] }})
.then(chat => { })
But it seems that every time it gives me the chat that contains at least one of the IDs I mentioned in the query.
So I've tried to change it to this but with no luck:
Chat.findOne()
.where({ users: { $in: [req.user_id] }})
.where({ users: { $in: [receiver._id] }})
.then(chat => { })
I need to find the chat that contains both of the user ID's inside the users array otherwise I expect for a null value.
How can I achieve this goal?
Thanks!
This is the way $in works - returns the document when at least one value matches. You should use $all instead:
Chat.findOne({ users: { $all: [req.user_id, receiver._id] }})

How to Query a nested child from firebase angular?

// my db structure now
rcv : {
visible: 'all',
ids: [
[0] : userId,
[1] : user2Id ]
}
this is how i query to get the data it works.
//service.ts
getAlbumByUserId(userId) {
return this.afs.collection('albums', ref => ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', userId)).valueChanges();
}
//component.ts
this.service.getAlbumByUserId(this.userId);
but i want to set the structure like this but i don't know how to query nested objects in firebase
// database structure
rcv : {
visible: 'all',
ids: {
userId: {
id: userId
}
user2Id: {
id: user2Id
}
}
}
You're looking for the array-contains operator, which can check if a field that is an array contains a certain value.
You're already using the correct array-contains operator, but not with the correct syntax. The array-contains operator checks whether any element of your array is exactly the same as the value you pass in. So you need to pass in the complete value that exists in the array:
ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', { id: userId })
As you add more data to the array, it may become unfeasible to reproduce the entire array element for the query. In that case, the common approach is to add an additional field where you keep just the IDs.
So you'd end up with one field (say rcv.users) where you keep all details about the receiving users, and one field (say rcv.ids) where you just keep their IDs, and that you use for querying.

Mongoose findOneAndUpdate on array of subdocuments

I'm trying to replace an array of sub-documents with a new copy of the array.
Something like...
var products = productUrlsData; //new array of documents
var srid = the_correct_id;
StoreRequest.findOneAndUpdate({_id: srid}, {$set: {products: products}}, {returnNewDocument : true}).then(function(sr) {
return res.json({ sr: sr}); //is not modified
}).catch(function(err) {
return res.json({err: err});
})
The products var has the correct modifications, but the returned object, as well as the document in the db, are not being modified. Is this not the correct way to replace a field which is an array of subdocuments? If not, what is?
I am a bit late to the party plus I am really in a hurry -- but should be:
StoreRequest.updateOne(
{ _id: srid },
{ $set: { 'products.$': products }},
{ new: true });
I couldn't make it work with findOneAndUpdate but the above does work.

Mongoose: Add more items to existing object

Using Mongoose, How can I add more items to an object without replacing existing ones?
User.findOneAndUpdate(
{ userId: 0 },
{ userObjects: { newItem: value } }
);
The problem with above code is that it clears whatever was there before and replaces it with newItem when I wanted it just to add another item to userObjects(Like push function for javascript arrays).
Use dot notation to specify the field to update/add particular fields in an embedded document.
User.findOneAndUpdate(
{ userId: 0 },
{ "userObjects.newerItem": newervalue } }
);
or
User.findOneAndUpdate(
{ userId: 0 },
{ "$set":{"userObjects.newerItem": newervalue } }
);
or Use $mergeObjects aggregation operator to update the existing obj by passing new objects
User.findOneAndUpdate(
{"userId":0},
[{"$set":{
"userObjects":{
"$mergeObjects":[
"$userObjects",
{"newerItem":"newervalue","newestItem":"newestvalue"}
]
}
}}]
)
According to your question, i am guessing userObjects is an array.
You can try $push to insert items into the array.
User.findOneAndUpdate(
{ userId: 0 },
{ $push : {"userObjects": { newItem: value } }},
{safe :true , upsert : true},function(err,model)
{
...
});
For more info, read MongoDB $push reference.
Hope it helps you. If you had provided the schema, i could have helped better.
Just create new collection called UserObjects and do something like this.
UserObject.Insert({ userId: 0, newItem: value }, function(err,newObject){
});
Whenever you want to get these user objects from a user then you can do it using monogoose's query population to populate parent objects with related data in other collections. If not, then your best bet is to just make the userObjects an array.

Categories

Resources