18 Nov 2009

Compiling Mustache Views for Mustache.js

I’ve been investigating the usage of Mustache, the dead-simple templating language with the funny name, and one very enticing aspect of Mustache is that it’s so simple it can easily be ported across languages. Mustache.js is a Javascript implementation of Mustache that works based on a simple JSON hash and a string template. Wouldn’t it be awesome if we could use the same Mustache template on the server side and in Javscript without having to construct two sets of logic? Yes it would, and here’s how.

Creating a Serializable Mustache

Mustache works by calling methods from the template on the Mustache view object. There are no arguments, no filters, just plain old Ruby methods. Mustache.js works by reading values from a Javascript object or evaluating functions on
that object. So, if we want to use the same logic to power both our Ruby-built Mustaches and our Javascript-built Mustaches, all we need to do is convert that logic to JSON!

Now, you could write a #to_json method for each of your Mustache views, but where’s the DRYness in that? Instead let’s create a Serializable Mustache that automatically catalogs the methods that need to be serialized to JSON:

module Mustache::Serializable
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    def serializable_methods
      public_instance_methods(false)
    end
  end

  def serializable_hash
    hash = self.class.serializable_methods.inject({}) do |result, method|
      # Symbolize the method to work better with the Mustache Context.
      result[method.to_sym] = self.send(method)
      result
    end
    
    hash.merge!(self.context)
  end

  def to_json
    serializable_hash.to_json
  end
end

That was easy enough! What we’re doing is compiling a list of methods that we want to serialize by saying ‘any public method that is declared in this specific Mustache class should be serialized’, constructing a hash from those methods, merging in the Mustache’s context, and providing a JSON representation of that hash.

Rendering in Ruby

Now that you have a simple way to serialize to JSON, we need to actually be able to render this Mustache in it’s server-side native form: Ruby. Here let’s say we’re working on a Sinatra application since it has probably the simplest setup. We’re not going to use the built-in Sinatra support from Mustache because it will be clearer in the code if we don’t. Here’s the entire application (make sure you somehow include the serializable code from above):

require 'rubygems'
require 'sinatra'
require 'mustache'

class Person < Mustache
  include Mustache::Serializable
  
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
  
  def formal_name
    "#{@last_name}, #{@first_name}"
  end
  
  def initials
    "#{@first_name[0..0]}.#{@last_name[0..0]}"
  end
  
  template = <<-HTML
    <dl>
      <dt>Formal Name:</dt> <dd></dd>
      <dt>Initials:</dt> <dd></dd>
    </dl>
  HTML
end

get '/person/:last_name/:first_name.:format' do
  @mustache = Person.new(params[:first_name], params[:last_name])
  @mustache.to_html
end

Rendering in Javascript

blog comments powered by Disqus