We recently ran into an interesting situation in a Meteor application we were building for a client.

The application had several types of users. We wanted each type of users to have a distinct set of helpers (defined with the Collection Helpers package).

Unfortunately, Meteor’s heavy use of global variables and the inability to define multiple collection references for a single MongoDB collection made this a more complicated task than we hoped.

Buyers and Sellers

To get a better idea of what we’re talking about, imagine we have “buyers” and “sellers”. Both of these are normal users, so they’ll reference the Meteor.users collection:


Buyers = Meteor.users;
Sellers = Meteor.users;

Now let’s define a few helpers:


Buyers.helpers({
  buy() { ... },
  history() { ... }
});

Sellers.helpers({
  sell() { ... },
  history() { ... }
});

Let’s imagine that buy on Buyers carries out a purchase, and history returns a list of all purchases that buyer has made. Similarly, sell on Sellers carries out a sale, and history returns a list of sales that seller has made.

A Buyer’s Seller History

We can call sell on a Seller, as expected:


let seller = Sellers.findOne({ ... });
seller.sell();

Similarly, we can call buy on a Buyer:


let buyer = Buyers.findOne({ ... });
buyer.buy();

We can also call history on both buyer and seller. However, when we call history on our seller, we don’t get a list of their sales. Instead, we get a list of their purchases.

If we dig a little more, we’ll also notice that we can call sell on our buyer, and buy on our seller.

This is definitely not what we want. These two distinct types of users should have totally separate sets of helpers.

Supersets of Helpers

These issues are happening because we’re defining two sets of helpers on the same Meteor.users collection. After the second call to helpers, Meteor.users has a buy helper, a sell helper, and the seller’s version of the history helper (the buyer’s history was overridden).

Even though we’re using different variables to point to our “different” collections, both variables are pointing to the same collection reference.

Our Meteor.users collection now has a superset of helper functions made up of the union of the Buyers and Sellers helpers.

Cloned Collection References

After considering a few more architecturally complicated solutions to this problem, we realized that an easy solution was sitting right under our noses.

Instead of having Buyers and Sellers reference the Meteor.users collection directly, we could have Buyers and Sellers reference shallow clones of the Meteor.users collection:


Buyers = _.clone(Meteor.users);
Sellers = _.clone(Meteor.users);

This way, each clone would have it’s own internal _helpers function which is used to transform the database document into an object usable by our Meteor application.

Calling Buyers.helpers will define helper functions on the Buyers collection reference, not the Sellers or Meteor.users collection references. Similarly, Sellers.helpers will set up a set of helper functions unique to the Sellers collection reference.

Now calling buyer.history() returns a list of purchases, and seller.history() returns a list of sales. The sell helper doesn’t exist on our buyer user, and buy doesn’t exist on our seller.

Perfect!

Final Thoughts

While this solution worked great for our application, it might not be the best solution to your problem.

Cloning collection references is a delicate thing that may not play nicely with all collection functionality, or all collection-centric Meteor packages.

Also note that deep cloning of collection references does not work at all. While we haven’t looked under the hood to find out what’s going on, we assume that it has to do with breaking callback references or something along those lines.

If you’re facing a problem like this, try to work out a solution that operates within the design principles of Meteor before hacking your way around them. But if all else fails, remember that you have options.