Search code examples
javascriptmongodbmeteormeteor-blaze

Meteor: How can I create a many-to-many relationship between two collections linked by ids?


I have two collections. One is called Posts and the other is called Categories. In the Posts collection, are individual posts which have an id. Shown in the picture as id: 1291, for this particular post, which is an integer, not a string.

enter image description here

The second collection is the Categories collection which contains the categories each of the posts belong to, most importantly, within each category in the Categories collection is an array of the posts that belong to that category. Pay attention to posts.ID which in this case is ID: 1291, This ID is the post id in the Posts collection

Summery.

  • There is a Posts collection which has individual posts. Those individual posts belong to different categories.
  • There is also the Categories collection which has the individual categories. In each category is an array of the posts that belong to that category but with few post objects I can utilise.

enter image description here

THE PROBLEM

I have a flow that goes like this.

The user scrolls through a list of categories and clicks on a category from the Categories collection with the goal of viewing the posts within that category.

<template name="category">
    {{#each categories}}
      <span class="title"><a href="/category/{{_id}}">{{name}}</a></span>
    {{/each}}
</template>

As it stands, When the user performs this action, they view the posts that are already in category that belongs to the Categories collection (remember the posts array within the category collection) like this

Template.singleCategory.helpers({
  categories() {
    var id = FlowRouter.getParam('_id');
    return Categories.find({
      _id: id
    }, {
      sort: {
        timestamp: 1
      },
      limit: 100
    });
  }
});

But these posts in the Categories collection do not have certain key objects found in the Posts collection, plus I want to get rid on the posts in the categories.

Goal

Instead of using these posts found in the Categories collection, I would like to use the posts found in the Posts collection and I would like to create the relationship using the similar Ids they share. So when a user clicks on a category, the posts they should see should come from the Posts collection, linked by the post ID in the Categories collection which is identical to the post id in the Posts collection.

Code

Here is the code. I'm trying to say the following:

In this single category being viewed by the user after they clicked it from the list of other categories, do not show the posts embedded in the Categories collection, Instead, return the Posts collection. In this Posts collection, find each post id, then match it to ID from category.posts within the Categories collection, then Show the objects from Posts collection, not category.posts.

  Template.singleCategory.helpers({
      posts(){
        return Posts.find({ ID: {$in: this.posts }});
      }
    });

However, I cannot seem to get it to work. I'm getting the error

Exception in template helper: Error: $in needs an array

the posts in the category collection are an array.

how can I solve this?

Edited

Building on the code in the answer section I had to treat category.posts so its an array. I'm getting the results I want, well, to an extent, some posts are not coming through. I wonder, could this code be better?

Template.Category.helpers({
  categories(){
    var id = FlowRouter.getParam('_id');
//create a variable with the correct category by id
var category = Category.findOne(id, {
  sort: {
    timestamp: 1
  },
  limit: 100
});
console.log(category);
//target ID within posts
var postArray = category.posts;
console.log(postArray);
//treat the object, clean it up to prepare it for becoming an array
      var postArrayStr = JSON.stringify(postArray).replace(/ID/g, '').replace(/\[|\]/g, '').replace(/""/g, '').replace(/:/g, '').replace(/\{|\}/gi, '');
//create an array
      var idsArray = postArrayStr.split(',').map(function(item) {
        return parseInt(item, 10);
      });
//match it to posts id
      var matchi = Posts.find({
  id: {
    $in: idsArray
  }
}).fetch();
      //
      console.log(matchi);
      //return it
      return matchi;

  }

}); 

Solution

  • What I would do is having in the category collection an array with just the post mongo's _id field (which is unique).

    Then in the helper you can query the post collection to return all the posts with those ids.

    So it would be something like this:

    Template.singleCategory.helpers({
      posts(){
        var id = FlowRouter.getParam('_id');
        var category = Category.findOne(id, {sort: {timestamp: 1},limit: 100 })
    
        var postArray = category.posts
        // example --> ["rCBWsKLCAWghBRJg4", "yMDtnDSo43ZDEcqpu", "cQeJntQtvwiHpgdZ9"]
    
        return Posts.find( {_id: {$in: postArray}});
        // it will return an array oj objects where each object is a post from the posts collection : [Object, Object, Object]
      }
    });
    

    There are better way to do it but I think this solution may fix your problem without change too many things