Creating a simple ORM in Meteor with transform

One the main challenges for new programmers learning Meteor.js is how to work with data coming from MongoDB server to the client in an object-oriented way. It’s not obvious when you first start learning Meteor how to wrap a Collection (a Meteor equivalent of a “Model” in the model-view-controller paradigm) into a data format you can easily work with.

In a standard MVC framework like Rails, you don’t have to worry about building your own abstractions to work with data flowing from backend to the UI more easily. Active Record handles it all for you in Rails. In Meteor, we don’t have an equivalent built-in abstraction. Meteor’s off-the-shelf functionality keeps the model layer fairly lean, and instead uses plain JavaScript to represent MongoDB documents. But all is not lost! Below, I’ll explain how you can modify raw server output to the client with a use of Meteor’s built-in transform method.

First, I’d like to start off by showing a common way you would handle class creation in JavaScript. The core concept that I’m going to go over is inheritance. Since JavaScript is a prototype-based language (unlike Ruby or Java, which are class-based), it doesn’t have a notion of classes per se. We can declare a class the same way we would declare a function:

function Teletubby() {};

The proper name for this class-like object in JavaScript is a constructor. In order to modify constructor’s properties, you have to do it via constructor’s property named prototype:

function Teletubby() { // new constructor!
}

Teletubby.prototype.sayEhoh = function() {
return "Eh-oh!";
};

var tinkyWinky = new Teletubby();
var laaLaa = new Teletubby();

tinkyWinky.sayEhoh(); // "Eh-oh!"
laaLaa.sayEhoh(); // "Eh-oh!"

Now we can successfully instantiate new teletubbies, and have them say Eh-oh! in a semantically meaningful way. We can also overwrite the inherited sayEhoh() method for each instance of a new Teletubby object, all while keeping our code clean and organized.

How do we now achieve the same effect working in Meteor? The answer is: using transform to add computed properties we can work with on the client side to our Collection object.

First, let’s create the Collection to work with:

lib/collections/teletubbies.js
Teletubbies = new Mongo.Collection('teletubbies', {
transform: function(doc) {
return ( new Teletubby( doc ) );
}
});

Now let’s publish the collection to the client via publication:

server/publications/publications.js
Meteor.publish(‘teletubbies’ , function() {
return ( Teletubbies.find() );
});

On the client, Meteor gives us a mini-database called Minimongo that accepts Collection data from the server via subscription(using Meteor.subscribe):

lib/collections/teletubbies.js
if (Meteor.isClient) {
Meteor.subscribe(‘teletubbies’);
}

Now, let’s jump right into transform. Here’s explanation of the property in the Meteor documentation:

We can now add a method to our Collection via prototype like this:

// doc is documents data coming from MongoDB
Teletubby = function (doc) {
_.extend(this, doc);
};
_.extend(Teletubby.prototype, {   
sayEhoh: function () {
return "Eh-oh!";
}
});

Note that new Mongo.Collection does not create a new table in the MongoDB database. It instead creates an object that represents your MongoDB collection (which may or may not already exist). The actual collection gets created when you insert a document object like this in the browser console:

Teletubbies.insert({name: "tinkyWinky"});
Teletubbies.insert({name: "laaLaa"});

We can now find the collection on the client and call the method that we created on it:

Teletubbies.findOne({name: "tinkyWinky"}).sayEhoh(); // "Eh-oh!"
Teletubbies.findOne({name: "laaLaa"}).sayEhoh(); // "Eh-oh!"

Here’s what it looks like in the console:

To the best of my knowledge, transform is still not a common way of manipulating data in Meteor applications. While under-represented, I think it’s a nifty tool that can help make your code more maintainable and organized in the long run. What is your opinion about transform? Is it commonly used in production, or more of a novelty at this time?

Feedback is much appreciated!