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.