Associate each model returned by a Meteor collection's subscription

150 views Asked by At

In a Meteor application I am currently working on, some collections may have multiple simultaneous subscriptions (i.e. one to fetch initially, another when searching more data, etc.) Naturally, I do not want to have the entire collection downloaded to the client, so I'm relying on subscriptions manager and a need-to-have approach.

The problem is that, once the data is fetched, the collection on the client contains the entire content of all subscriptions, and I am not sure how I can discern which data was fetch from what subscription (i.e. which data was fetched initially, and which was searched, etc.)

I have thought adding a subscription UID to each fetched data (an array, since some data can be fetched from multiple subscriptions), but this seems hacky and prone to cleanup hell.

Is there a solution to associate and identify which subscription fetched which data?


Update

So fare, this answer seem to solve part of the problem, however this is not the kind of solution I find "clean".

// ctx being the Metheor.subscribe context (i.e. const ctx = this;)

function transform(doc) {
  if (searchKey) {
    doc._searchKey = searchKey;
  }
  return doc;
}

const cursor = InventoryItems.find(filter, options);
const observer = cursor.observe({
  added(doc) {
    ctx.added(InventoryItems._name, doc._id, transform(doc));
  },
  changed(newDoc, oldDoc) {
    ctx.changed(InventoryItems._name, newDoc._id, transform(newDoc));
  },
  removed(oldDoc) {
    ctx.removed(InventoryItems._name, oldDoc._id);
  }
});

ctx.onStop(() => {
  observer.stop();
});

return cursor;
1

There are 1 answers

0
MasterAM On

This answer describes a way to publish data to a client-only collection.

If you want to separate some of the data, you can publish it to a different collection:

//client

const TempCollection = new Meteor.Collection('temp', {defineMutationMethods: false});

Meteor.subscribe('temp.query', 'queryParams');

This defines a collection without setting mutation methods, so any changes done to it on the client are not sent to the server (as it would be pointless). Note that the collection name (temp in this case) should be used in the server publication.

// server
const ActualCollection = new Mongo.Collection('real_collection_name');

function publishForTarget(sub, cursor, targetName) {
    return Mongo.Collection._publishCursor(cursor, sub, targetName);
}

Meteor.publish('temp.query', function(/*params*/) {
    let query = {/*...*/};
    let targetName = 'temp'; // or get it from the client for dynamically-named client-side collections. 
    let cursor = ActualCollection.find(query);    
    publishForTarget(this, cursor, targetName);

    return this.ready();
});

This uses the undocumented Mongo.Collection._publishCursor method, which is also used by the standard publication when cursor(s) are returned.

Its implementation:

Mongo.Collection._publishCursor = function (cursor, sub, collection) {
  var observeHandle = cursor.observeChanges({
    added: function (id, fields) {
      sub.added(collection, id, fields);
    },
    changed: function (id, fields) {
      sub.changed(collection, id, fields);
    },
    removed: function (id) {
      sub.removed(collection, id);
    }
  });

  // We don't call sub.ready() here: it gets called in livedata_server, after
  // possibly calling _publishCursor on multiple returned cursors.

  // register stop callback (expects lambda w/ no args).
  sub.onStop(function () {observeHandle.stop();});

  // return the observeHandle in case it needs to be stopped early
  return observeHandle;
};

It observes changes to the cursor and calls subscription life-cycle methods (which translate to DDP mutation messages) when things change. We are replacing the real collection name by the targetName when using it.

It means that all of the results will be published to the 'temp' collection on the client and will be separate from any other data that are queried normally and are published to 'real_collection_name'.