• Meteor
  • Tutorial

Transforming Collection Documents

In this screencast, I'm going to talk about the transform option that can be passed to a new Meteor collection in the options object. The transform option lets us provide a function, and that function will get called with the raw document from our Mongo collection for each document that's returned when you call fetch or find one in a collection, or before the document is passed into an observe, allow, or deny call back. The biggest use case I can think of for the transform option is to create a model layer so that we can instantiate new model instances for each of our documents.

But by taking a function as a value, we can have a little bit more flexibility because we don't necessarily need to create new model instances. We could do whatever we want inside this function, as long as we return a document at the end of it. So let's get into the code. In the browser on the right, I have a list of products. And each product has a name, a description, and an integer price that's stored in cents in the database so that we don't get any rounding errors.

On the left, I have a my body tag that just renders a product list. And in the product list, I loop through the products and render the product item template for each one. And in the product item template, you can see how I print out the name of the product, the description, and the raw price from the database. Let's jump over and look at the JavaScript.

In my JavaScript file, at the top I create my new collection called Product Collection. And in the database, I've called it Products. In the server code, I do some things to seed the collection with a few products and a few random prices. And you can see here that I'm just storing the prices in cents. But the key thing to look at in the server code is down here, I'm publishing the products by returning productcollection.find.

And then down in my server code, I'm subscribing to the products collection. And here's my helper method on the products list, where I return the cursor from calling productcollection.find. So what I would like to do is I would like to be able to show the price in dollars instead of in cents. And so one way to accomplish that is I could create another helper on my product item called format price or something like that, that just takes this number and divides it by 100.

But I would like a method that's a little bit closer to the data so that if I use the data in multiple views, I don't have to duplicate that code over and over. So inside of my product collection, what I can do is provide a transform function for the data. So let's do that. So I can add my transform function as an option in the second parameter of Meteor collection. And the function will take a Mongo document as a parameter. And all we need to make sure we do is to return the modified document and return an object from this function, the transformed document, or it could be a new model instance.

But for now, what I want to do is just modify the price in place. And so to do that, I'm going to say the document.price is equal to-- and what we'll do is we'll take the price, divide it by 100, and then just call the two fixed method with two as a parameter. And this will just give us two decimal places.

OK, great. So over on the right, you can see that our prices have how been converted into dollars. And it looks a little bit more reasonable. Now what we've done in this example is we've modified the price in place on the document and then just returned the document in the transform function. But what we might want to do is actually have a little bit more object orientation by creating a product constructor function or a product class.

So let's do that. Let's create a constructor function called product. And it will take a document as a parameter to the constructor function. And then we'll use the underscores extend method to merge the document into this product object. And then down in my transform function, I'm going to remove this line. And instead of returning the document directly, I'm going to return a new instance of my product constructor.

OK. So now over on the right in the browser, I'm back to where I was before with just showing cents. And so what we can do is add a method to the product prototype. And as a stylistic thing, I like to just reset the prototype. And when I do that, I just set the constructor back to the product, just to make sure that it doesn't get wiped out.

But let's create a method called format price. And what format price will do is exactly what we had before, except instead of using document, we'll just use the price that is set on this instance. Now what we need to do is we need to go over to the HTML. And instead of just calling price directly, let's call format price.

Great. And so this format price method is available here inside of our handlebars helper because our data actually has it as a property. And we can look at that by-- let's just grab a product in the console over in the right. So I'll say product is equal to products, productcollection.find1. And when I do this, you can see that the document that comes out of mini Mongo is getting transformed into a new product instance. And that product instance has a method on it called format price.

But I could even get a little bit fancier here. I might want my format price method to take a parameter, where I can specify that I want it provided in dollars or in cents. So let's open up our JavaScript again and modify the format price method to take a format. And then I'll use a switch statement. And if it's cents, I'll just return the raw price. And as a default, I'll return the dollars.

So in conclusion, you can see that this transform option to a product collection gives us quite a bit of flexibility. We can modify the document in place, we can call other methods, or we could return a new instance of a model class, like product. In this case, we created a product constructor or a product class, and inside it we have a method called format price. And we can call that method right from within our handlebars helpers and not have to create template helpers for each type of thing that we want to do to the data. In the next couple of episodes, we'll look at some more advanced cases of creating a model class and using it in combination with EJSON.