Finding documents with $in, using an array of another document - mongodb

I'm trying to built a friendship system with Meteor.js, Blaze and Mongodb. I'm at the point where I want to display the friends of a user on his profile page.
I have the collection "users" that has the field "friends" which is the array the names of other uses can be pushed in or pulled out.
Simplified example of a user document:
"name" : "bob"
"friends" : ["value of name-field of user-document1", "value of name-field of user-document2", "etc."]
I tried to put this friends array inside a hidden input on the profile and use it from there to create a iteration on the profile-template.helpers:
<input id="friends" type="hidden" value="{{user.friends}}">
friends() {
var friends = document.getElementById("friends").value;
var friendsArray = friends.split(",");
return Users.find({name:{$in: friendsArray},})
},
But thats not how it works. How can I use/get this field, that contains the friends-array for this $in operation to get the other users that are friends with bob? I also tried this.friends But that only seems to work for iterations.
I could use
{{#each friend in user.friends}}
{{friend}}
{{/each}}
to get the names of the friends at least but I wanted to include the little avatar of those friends on the display-friends section as well, so that wouldn't do.
Edit: The user variable is defined in the Template.user.helpers as:
user: ()=> {
var user = FlowRouter.getParam('user');
return Users.findOne({name: user});
},
I tried to use this.data.char.friends inside the friends-function like so
friends() {
return Users.find({
name: { $in: (this.data.user.friends) }
});
},
But it gave me an console.log error:
Exception in template helper: ReferenceError: user is not defined

Try this aggregation query, rather than doing multiple calls to DB for fetching required data :
db.getCollection('Users').aggregate([{ $match: { name: 'bob' } },
{
$graphLookup: {
from: "Users",
startWith: "$friends",
connectFromField: "friends",
connectToField: "name",
as: "friendsDetails"
}
}, { $project: { name: 1, friends: 1, friendsDetails: { $filter: { input: '$friendsDetails', as: 'item', cond: { $ne: ['$$item.name', 'bob'] } } } } }])
Reason why we've $project and $filter is by default $graphLookup will return the requested User as well in list of friendsDetails, used these to remove it from end result.

Ah no, it was actually more simple than that.
friends() {
var name = FlowRouter.getParam('user');
return Users.find( { friends: { $in: [name] } } );
},
This one displays the friends on the profile just like that. I just have to figure out why it does that ...

Related

Can't remove object in array using Mongoose

This has been extensively covered here, but none of the solutions seems to be working for me. I'm attempting to remove an object from an array using that object's id. Currently, my Schema is:
const scheduleSchema = new Schema({
//unrelated
_id: ObjectId
shifts: [
{
_id: Types.ObjectId,
name: String,
shift_start: Date,
shift_end: Date,
},
],
});
I've tried almost every variation of something like this:
.findOneAndUpdate(
{ _id: req.params.id },
{
$pull: {
shifts: { _id: new Types.ObjectId(req.params.id) },
},
}
);
Database:
Database Format
Within these variations, the usual response I've gotten has been either an empty array or null.
I was able slightly find a way around this and accomplish the deletion by utilizing the main _id of the Schema (instead of the nested one:
.findOneAndUpdate(
{ _id: <main _id> },
{ $pull: { shifts: { _id: new Types.ObjectId(<nested _id>) } } },
{ new: true }
);
But I was hoping to figure out a way to do this by just using the nested _id. Any suggestions?
The problem you are having currently is you are using the same _id.
Using mongo, update method allows three objects: query, update and options.
query object is the object into collection which will be updated.
update is the action to do into the object (add, change value...).
options different options to add.
Then, assuming you have this collection:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
},
{
"_id": 3
}
]
}
]
If you try to look for a document which _id is 2, obviously response will be empty (example).
Then, if none document has been found, none document will be updated.
What happens if we look for a document using shifts._id:2?
This tells mongo "search a document where shifts field has an object with _id equals to 2". This query works ok (example) but be careful, this returns the WHOLE document, not only the array which match the _id.
This not return:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
}
]
}
]
Using this query mongo returns the ENTIRE document where exists a field called shifts that contains an object with an _id with value 2. This also include the whole array.
So, with tat, you know why find object works. Now adding this to an update query you can create the query:
This one to remove all shifts._id which are equal to 2.
db.collection.update({
"shifts._id": 2
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example
Or this one to remove shifts._id if parent _id is equal to 1
db.collection.update({
"_id": 1
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example

Find documents matching ObjectIDs in a foreign array

I have a collection Users:
{
_id: "5cds8f8rfdshfd"
name: "Ted"
attending: [ObjectId("2cd9fjdkfsld")]
}
I have another collection Events:
{
_id: "2cd9fjdkfsld"
title: "Some Event Attended"
},
{
_id: "34dshfj29jg"
title: "Some Event NOT Attended"
}
I would like to return a list of all events being attended by a given user. However, I need to do this query from the Events collection as this is part of a larger query.
I have gone through the following questions:
$lookup on ObjectId's in an array - This question has the array as a local field; mine is foreign
MongoDB lookup when foreign field is an array of objects - The array is of objects themselves
MongoDB lookup when foreign field is an array
I have tried various ways of modifying the above answers to fit my situation but have been unsuccessful. The second answer from the third question gets me closest but I would like to filter out unmatching results rather than have them returned with a value of 0.
My desired output:
[
{
_id: "2cd9fjdkfsld"
title: "Some Event Attended"
},
]
One option would be like this:
db.getCollection('Events').aggregate({
$lookup: // join
{
from: "Users", // on Users collection
let: { eId: "$_id" }, // keep a local variable "eId" that points to the currently looked at event's "_id"
pipeline: [{
$match: { // filter where
"_id": ObjectId("5c6efc937ef75175b2b8e7a4"), // a specific user
$expr: { $in: [ "$$eId", "$attending" ] } // attends the event we're looking at
}
}],
as: "users" // push all matched users into the "users" array
}
}, {
$match: { // remove events that the user does not attend
"users": { $ne: [] }
}
})
You could obviously get rid of the users field by adding another projection if needed.

Mongodb cursor to Json

So my problem is that I cant push a result of findOne() method to array in document.
For example, I want to add another user to some array (project participants) like this :
var user =db.users.findOne({_id:"123456"})
db.projects.update({_id:'abcde'},{$set:{$push:{participants:user}}})
how can I fix that ?
You need your update query like so:
db.test.update({
_id: 'abcde'
},
{
$push: {
participants: { name: 'Bob'}
}
})

meteor query for all documents with unique field

I want to do exactly what this SO question gets at but with Meteor on the server side:
How do I retrieve all of the documents which HAVE a unique value of a
field?
> db.foo.insert([{age: 21, name: 'bob'}, {age: 21, name: 'sally'}, {age: 30, name: 'Jim'}])
> db.foo.count()
3
> db.foo.aggregate({ $group: { _id: '$age', name: { $max: '$name' } } }).result
[
{
"_id" : 30,
"name" : "Jim"
},
{
"_id" : 21,
"name" : "sally"
}
]
My understanding is that aggregate is not available for Meteor. If that is correct, how can I achieve the above? Performing post-filtering on a query after-the-fact is not an ideal solution, as I want to use limit. I'm also happy to get documents with a unique field some other way as long as I can use limit.
There is a general setup you can use to access the underlying driver collection object and therefore .aggregate() without installing any other plugins.
The basic process goes like this:
FooAges = new Meteor.Collection("fooAges");
Meteor.publish("fooAgeQuery", function(args) {
var sub = this;
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var pipeline = [
{ "$group": {
"_id": "$age",
"name": { "$max": "$name" }
}}
];
db.collection("foo").aggregate(
pipeline,
// Need to wrap the callback so it gets called in a Fiber.
Meteor.bindEnvironment(
function(err, result) {
// Add each of the results to the subscription.
_.each(result, function(e) {
// Generate a random disposable id for aggregated documents
sub.added("fooAges", Random.id(), {
"age": e._id,
"name": e.name
});
});
sub.ready();
},
function(error) {
Meteor._debug( "Error doing aggregation: " + error);
}
)
);
});
So you define a collection for the output of the aggregation and within a routine like this you then publish the service that you are also going to subscribe to in your client.
Inside this, the aggregation is run and populated into the the other collection ( logically as it doesn't actually write anything ). So you then use that collection on the client with the same definition and all the aggregated results are just returned.
I actually have a full working example application of a similar processs within this question, as well as usage of the meteor hacks aggregate package on this question here as well, if you need further reference.

Mongoose Update Instance from Nested Array

In Mongoose, lets say I have a User object pulled from MongoDB and that user has an array of Interests. Now I get an instance of one of that user's Interests.
var user = ...
var interest = ...
... //Make some changes to interest.
How do I update that Interest object (after making some changes to it) within the User array in the DB?
Edit
Here is my current code. It doesn't work and doesn't give an error.
User.update(
{
'_id': user._id,
'interests._id': interest._id
},
{
'$set': {
'interests.$.xyzProperty': interest.xyzProperty
}
},
function(err,obj){//some error checking}
);
If you set an if for each interest, you can access the interest by the $ operator.
user document
{
_id: ObectId('54b568531ef35a7c348f21f2'),
interests: [
{
_id: 12345,
title: 'Tacos',
description: 'I Love tacos'
},
{...},
{...},
]
}
If I know which interest sub document I want to update, I simply query it like so:
UserModel.find({_id: ObectId('54b568531ef35a7c348f21f2'), 'interests.i_d': 12345}).lean().exec(function (err, user) {
var interest = ... //find specific interest
interest.description = 'I love tacos... Like, a lot'.
UserModel.update(
{
_id: user._id,
'interests._id': interest._id
},
{
$set: {
'interests.$.description': interest.description
}
},
function (err, update) {
console.log(err, update);
}
);
});
This uses the $ positional operator and updates the specific sub document(or item in an array).