How to create a Mongo query with multiple collections?

Hey guys,

I’ve got a followers system that needs to return an array of followers / followings based on a given user ID.

The collections are set up like this :

FollowersCollection :

{
    "_id" : "5t5HdBGerKoh6amTH",
    "uid" : "gq33mc3Ws4agHrGtk",
    "fid" : "nskG2euomRcJEKBRs"
},
{
    "_id" : "sZcLBh86Gysge3yHy",
    "uid" : "T4xtun25GJQYjrrzu",
    "fid" : "CszsAuGfFXgLMi66"
}

uid = the user that has been followed
fid = the user that is doing the following

UserData Collection :

{
    "_id" : "7YBSEFogAffHdx7my",
    "uid" : "nskG2euomRcJEKBRs",
    "username" : "sam",
    "img" : "15344814695b76543d0fc705b76543d0fcb4.jpg",
    "name" : "Sam Hills",
    "country" : "FR",
    "about" : "I like things.",
    "updatedAt" : ISODate("2018-08-17T04:51:38.844Z"),
},
{
    "_id" : "aCru7hyhBdnnS7gSb",
    "uid" : "T4xtun25GJQYjrrzu",
    "username" : "dave",
    "img" : "15344635695b760e515b5655b760e515b5aa.jpg",
    "name" : "Dave Masterson",
    "country" : "UK",
    "updatedAt" : ISODate("2018-08-16T23:52:49.469Z"),
}

Every user has a UserData document which contains their profile info.

In the case of displaying a users followers I need to somehow JOIN or GROUP the two collections. I first need to query the FollowersCollection passing in the uid of the user that we want to display followers for. This would search the FollowersCollection matching the supplied uid to any record with a matching uid and return an array of all of the user ids (fid) that follow them.

I then need to query the UserData Collection by matching each fid to the uid in the UserData Collection which will return an array of “complete” followers which I can then map through and render in my component.

I’ve managed to get it working by breaking this process into separate components (I’m using React not Blade so template answers aren’t going to help me I’m afraid) :

  1. get and pass the user ID into comp 2
  2. get followers and pass props into comp3
  3. cycle through each props.fid and query the UserData Collection for the data of each follower and render it.

But this is unusable with Infinite Scroll (which I need).

So I really need the second component to return an array with the complete results of each follower with their data.

I’ve been trying all kinds of things for the past 6 hours and nothing seems to be working.

In SQL this is a simple process but with a non-relational database like Mongo this seems to be really difficult to achieve. I’ve read tons of suggestions for how to do similar things but none of them really explained how the query was working, (they just supplied the answer), which didn’t help me to understand how to use it in my particular use case.

This answer from SO looks like it should do what I need…

db.B.aggregate([
    { "$match": { "name": /.*aco.*/ } },
    {
        "$lookup": {
            "from": "A",
            "localField": "excel_template",
            "foreignField": "_id",
            "as": "bList"
        }
    }
])

But when I try this in the Mongo console…

db.followers.aggregate([{"$match": {"uid": "nskG2euomRcJEKBRs"}}, {"$lookup: {"from": "userdata", "localField": "fid", "foreignField": "uid", "as": "followers"}}])

It throws the following error :

2018-08-18T19:26:54.767+0100 E QUERY [thread1] SyntaxError: missing : after property id @(shell):1:79

Could somebody please give me some tips on how I could achieve this in one JOINED or GROUPED query please as this is driving me nuts :slight_smile:

EDIT

I tried to use distinct in the following Method (on the server just to try and get some results) :

'get.followers': function(v) {
      console.log(FollowersCollection.distinct("fid", {uid: v}));
}

And got the following error :

Exception while invoking method 'get.followers' TypeError: FollowersCollection.distinct is not a function

Starting to understand why this is such a difficult and confusing area for so may people now as Meteor doesn’t support all of the Mongo methods. Not much help when you’re trying to work something out. :frowning:

I can feel SQL luring me back in, it’s insane how difficult this is for something so simple.

EDIT 2

Making some progress so will keep updating this thread until I work it out incase there are others having a similar issue…

Managed to get distinct working by accessing the raw collection. This returns all of the ids of a users followers :

'get.followers': function(v) {
      FollowersCollection._collection.rawCollection().distinct("fid", {uid: v})
      .then(followerIds => console.log(followerIds));
 }

EDIT 3

Okay more progress, I am now getting my full array back :slight_smile: (server side at least). I’m assuming that this isn’t going to work client side so what would be the hack to get this data to the client?

'get.followers': function(v) {
      FollowersCollection._collection.rawCollection().distinct("fid", {uid: v})
      .then(followerIds => console.log(UserData.find({"uid": {"$in": followerIds}}).fetch()));
}

EDIT 4

On the home stretch now…

Method :

'get.followers': function(v) {
      return FollowersCollection._collection.rawCollection().distinct("fid", {uid: v})
      .then(followerIds => UserData.find({"uid": {"$in": followerIds}}).fetch());
}

In the component :

constructor(props) {
    super(props);

    const followers = this.getFollowers();
}
getFollowers() {
    Meteor.call('get.followers', "nskG2euomRcJEKBRs", function (error,result) {
      if (result) {
        console.log(result);
      }
    });
}

(Supplying a hard-coded uid at the moment for testing purposes)

Which is console logging the array to the client perfectly.
Now I just need to store the data and use it :slight_smile:

EDIT 5

Okay I give up. I’ve spent all day trying to do something so basic and common that virtually every website / app with any kind of data management requirements will need to do and I can’t get it working. WHY is it this hard? Virtually all of the answers / help I can find on the net are related to Blade (which is useless in my situation as I’m using React). This has driven me to the point of despair. I’ve tried it every way I can possibly think of and it just doesn’t paginate.

I’ve gone back to the 3 component set up now (instead of collating the data from both collections) but Infinite Scroll either loads all of the records or doesn’t load anymore than the PER_PAGE limit I’ve set. I have it working perfectly in other components where an entire collection is being queried .find({}), but as soon as you throw a query parameter into the mix it doesn’t work.

This is what I’m doing now :

const PER_PAGE = 5; //outside of the class

constructor(props) {
    super(props);

    this.loadFunc = this.loadFunc.bind(this);
}
componentWillMount() {
    this.page = 1;
}
loadFunc() {
    Meteor.subscribe('getfollowers', this.props.pid, PER_PAGE * (this.page + 1));
    this.page +=1 ;
    console.log("loading");
}
render() {
    if (this.props.allfollowers && this.props.pid) {
      return (
        <InfiniteScroll
          pageStart={0}
          loadMore={this.loadFunc}
          hasMore={true || false}>

          <div className="follower-list">
            {this.props.allfollowers.map(followers => <FollowerLATEST2 key={followers._id} followers={followers}/>)}
          </div>
        </InfiniteScroll>
      );
    }
    return null;
}
export default withTracker(props => {
  Meteor.subscribe('getfollowers', PER_PAGE);
  return {
    allfollowers: FollowersCollection.find({uid: props.pid}).fetch()
  };
})(FollowersListLATEST);

The Publish method for the collection :

Meteor.publish("getfollowers", function(v, PER_PAGE) {
    return FollowersCollection.find({uid: v}, { limit: PER_PAGE });
});

A test render method in the 3rd component FollowerLATEST2

render() {
    if (this.props.followers) {
      return (
        <div className="follower">
          {this.props.followers.fid}
        </div>
      )
    }
    return null;
}

But the above simply renders all of the matching records it finds, ignoring the limit.

Is there anybody out there using React with Meteor that can put me out of my misery please?