I want to make a publication with several additional fields, but I don't want to either use Collection.aggregate and lose my publication updates when the collection change (so I can't just use self.added in it either).

I plan to use Cursor.observeChanges in order to achieve that. I have two major constraints:

  1. I don't want to publish all the documents fields
  2. I want to use some of the unpublished fields to create new ones. For example, I have a field item where I store an array of item _id. I don't want to publish it, but I want to publish a item_count field with the length of my field array

Here comes the approach:

  1. I plan to chain find queries. I never did that so I wonder if it possible. The general (simplified) query structure would be like this: http://jsfiddle.net/Billybobbonnet/1cgrqouj/ (I cant get the code properly displayed here)

  2. Based on the count example in Meteor documentation, I store my query in a variable handle in order to stop the changes notification if a client unsubscribes:

self.onStop(function () {
  handle.stop();
});
  1. I add a flag initializing = true; before my query and I set it to true just before calling self.ready();. I use this flag to change my itemCount variable only if it is the publication is initialized. So basically, I change my switch like that:
switch (field) {
  case "item"
    if (!initializing)
      itemCount = raw_document.item.length;
      break;
  default:
}

I wanted to check that this approach is good and possible before committing into big changes in my code. Can someone confirm me if this is the right way to go?

Solution 1

It's relatively easy to keep fields private even if they are part of the database query. The last argument to self.added is the object being passed to the client, so you can strip/modify/delete fields you are sending to the client.

Here's a modified version of your fiddle. This should do what you are asking for. (To be honest I'm not sure why you had anything chained after the observeChanges function in your fiddle, so maybe I'm misunderstanding you, but looking at the rest of your question this should be it. Sorry if I got it wrong.)

var self = this;

// Modify the document we are sending to the client.
function filter(doc) {
  var length = doc.item.length;

  // White list the fields you want to publish.
  var docToPublish = _.pick(doc, [
      'someOtherField'
  ]);

  // Add your custom fields.
  docToPublish.itemLength = length;

  return docToPublish;                        
}

var handle = myCollection.find({}, {fields: {item:1, someOtherField:1}})
            // Use observe since it gives us the the old and new document when something is changing. 
            // If this becomes a performance issue then consider using observeChanges, 
            // but its usually a lot simpler to use observe in cases like this.
            .observe({
                added: function(doc) {
                    self.added("myCollection", doc._id, filter(doc));
                },
                changed: function(newDocument, oldDocument)
                    // When the item count is changing, send update to client.
                    if (newDocument.item.length !== oldDocument.item.length)
                        self.changed("myCollection", newDocument._id, filter(newDocument));
                },
                removed: function(doc) {
                    self.removed("myCollection", doc._id);                    
                });

self.ready();

self.onStop(function () {
  handle.stop();
});

Solution 2

To solve your first problem, you need to tell MongoDB what fields it should return in the cursor. Leave out the fields you don't want:

MyCollection.find({}, {fields: {'a_field':1}});

Solving your second problem is also pretty easy, I would suggest using the collection helpers packages. You could accomplish this easily, like so:

// Add calculated fields to MyCollection.
MyCollection.helpers({
  item_count: function() {
    return this.items.length;
  }
});

This will be run before an object is added to a cursor, and will create properties on the returned objects that are calculated dynamically, not stored in MongoDB.