Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging collections in Meteor

Tags:

meteor

Imagine you have multiple collections you want displayed on a single social news feed, for example Posts and Users (new signups). How would you reactively display both in a list sorted by creation date?

Idea 1 - merging in client

You publish the entities separately, and in the client, sort each of them by date, and merge them into a single sorted array, which you then display in an {{#each}} iterator. Problem: AFAIK this requires flattening the reactive cursors into static arrays, so now the page won't update. (Perhaps there's a way of making the page recalculate this array when any collection changes, salving this approach?)

Idea 2 - creating a new collection

You create a new collection, say FeedItems. When a new Post or User is created, you also create a new FeedItem and copy the relevant information into it. Displaying the items in the client is now very straightforward. Problem: Now there is no reactivity between the canonical objects and the FeedItem versions of them, so if someone changes their name, deletes a post, etc., this won't be reflected in the feed. (Perhaps there's a way of creating reactivity between collections to salvage this approach?)

Idea 3 - merging in the publication

Perhaps there's some way of sticking with the existing collections, but creating an additional 'newsFeed' publication which would somehow merge them. I haven't seen any way of doing this, however. I see in the docs that you can publish an array of collections, but AFAIK this is equivalent to publishing the same collections one at a time.

Is one of these approaches on the right track? Or is there another I haven't thought of?

like image 854
Zach Avatar asked Jan 23 '14 00:01

Zach


2 Answers

By far the easiest solution is to merge them on the client in a template helper. For example:

Template.dashboard.helpers({
  peopleAndPosts: function() {
    var people = People.find().fetch();
    var posts = Posts.find().fetch();
    var docs = people.concat(posts);
    return _.sortBy(docs, function(doc) {return doc.createdAt;});
  }
});

and the template (assuming people and posts have a name):

<template name="dashboard">
  <ul>
    {{#each peopleAndPosts}}
    <li>{{name}}</li>
    {{/each}}
  </ul>
</template>

This will be reactive because the helper is a reactive context so any changes to People or Posts will cause the returned array to be recomputed. Be aware that because you are not returning a cursor, any changes to either collection will cause the entire set to render again. This won't be a big deal if the length of the returned array is relatively short.

like image 191
David Weldon Avatar answered Nov 17 '22 22:11

David Weldon


Idea 1 seems to work out of the box. I created a new meteor project and change it as follows:

test.js:

Users = new Meteor.Collection("users");
Posts = new Meteor.Collection("posts");

if (Meteor.isClient) {    
  Template.hello.array = function () {
      var a = Users.find().fetch()
          .concat(Posts.find().fetch());
      return _.sortBy(a, function(entry) { return entry.votes; });      
  };
}

if (Meteor.isServer) {
  Meteor.startup(function () {
      // code to run on server at startup
      Users.insert({name: "user1", votes: 1});
      Users.insert({name: "user2", votes: 4});
      Users.insert({name: "user3", votes: 8});
      Users.insert({name: "user4", votes: 16});

      Posts.insert({name: "post1", votes: 2});
      Posts.insert({name: "post2", votes: 4});
      Posts.insert({name: "post3", votes: 6});
      Posts.insert({name: "post4", votes: 8});

  });
}

test.html:

<head>
  <title>test</title>
</head>

<body>
  {{> hello}}
</body>

<template name="hello">
  {{#each array}}
  <div>{{votes}} {{name}}</div>
  {{/each}}

</template>

This gives the expected list:

1 user1
2 post1
4 user2
4 post2
6 post3
8 user3
8 post4
16 user4

Then I did Users.insert({name: "new", votes: 5} in the console and got (reactively):

1 user1
2 post1
4 user2
4 post2
5 new
6 post3
8 user3
8 post4
16 user4
like image 29
Christian Fritz Avatar answered Nov 17 '22 20:11

Christian Fritz