Cooking Up A Custom Rails 3 Template

by Andrea Singh |
October 11, 2010

Rails 3 templates allow you to set up your work environment just the way you want it. For example, I prefer jQuery to Prototype, Haml to erb templates, and these days am using RSpec for testing. I also use Compass with Sass and Factory Girl for fixture replacement. Repeating the steps required to swap out these parts every time I generate a new rails app gets old pretty quickly. Templates are plain ruby files that automate these type of setup tasks.

Rails 3 template files can install plugins or gems, run rake tasks or migrations and even place boilerplate code into any file you specify. So besides setting up your basic Rails environment, you can instruct your template file to set up a user system, for example, by automatically installing the devise gem and running its generate command.

Templates have been completely overhauled in Rails 3. Besides sporting more commands, templates now share the API with generators. This makes sense seeing that they both involve generating new files or modifying existing ones. The internals have been rebuilt and now leverage the Thor gem in the background.

Available Generator Methods

To take full advantage of the new Rails 3 template API, you need to be familiar with what methods are available to you. For reference purposes, it is also beneficial to know where these methods are defined. The more general purpose methods now live in the Thor::Actions class. Some of these are:

copy_file(source, *args, &block)

create_file(destination, *args, &block)

get(source, *args, &block) -- download files from a remote url

prepend_file(path, *args, &block)

remove_file(path, config = {})

run(command, config = {}) -- used to run bash commands

Console Options

Thor::Shell::Basic provides several methods that enable you to capture and process user input:

rakefile("bootstrap.rake")doproject=ask("What is the UNIX name of your project?")<<-TASK namespace :#{project} do task :bootstrap do puts "i like boots!" end end TASKendrakefile("seed.rake","puts 'im plantin ur seedz'")

Executing stuff

Finally there are methods that execute a command:

git(command={}) -- Runs a command in git

generate(what, *args) -- Runs a generator from Rails or a plugin. script/rails generate #{what} [flattened args] is run in the background

rake(command, options={}) -- Runs the supplied rake task

readme(path) -- Reads the file at the given path and prints out its contents

plugin(name, options) -- Installs a plugin from github

Template Design

Now that we have an overview of the building blocks, we need to consider the base composition of the template. Because most templates are very opinionated in their choices of what to swap out, tweak, generate, etc., chances are slim that you will find one that perfectly suits your needs right out of the box. However, Rails 3 has made it very easy to customize existing templates for different setups without needing to create an entirely new one for each configuration.

The apply method takes a path variable which can point to either an external url or a local file. The contents of the file are read and executed using instance_eval. This allows you to separate out functionality you want to invoke into different component or "recipe" files. You make a directory to house each of the recipe files, e.g. one for "jquery", "cucumber", "haml", etc. In the template file you can then pick and choose which of these recipes you want to apply. Since it uses instance_eval, the apply method executes the content of the recipe file within the scope of the template file. This has the same effect as if you had placed the recipe code snippet directly into the template file.

As a basis for my own template, I forked an existing template on github that appealed to me because it was fairly simple and used this recipe approach. It came very close to the setup I needed, but more importantly, it afforded the flexibility for customizing the template.

To create a new Rails app based off of this template you would need to use the -m switch with the path to the templater.rb file. This is the actual template file that orchestrates all the things that happen after the default files and folders of the new app have been generated.

Defining And Modifying Recipes

In the main template file you can add new or remove existing recipes. The template code loops through the recipes you specify and applies each of them in turn.

What if you have recipes that you want to apply sometimes, but not other times? It is very easy to make a recipe optional or allow for live choices between different recipes.

The file core_extensions.rb extends the regular Rails::Generators::Actions with some custom methods. One of those additional methods is called load_options and this is where the template defines some choices and saves them to a Hash called @template_options:

In the template or recipe files this @template_options can later be queried to make decisions. In simpler cases, the same effect can also be accomplished with a simple yes/no question:

apply(recipe('cucumber'))ifyes?("Do you want to some cukes?")

Delayed Code Execution Using Lambdas

Some of the code in the recipe files depends on the presence of certain gems, meaning that it can only be executed after the gem in question has been installed. However, the recipes are being read (applied) before the gems are loaded. So how can you define the gem dependent code in the recipe file, but only run it after the gem is available? Lambdas to the rescue! Take the example of the recipe for compass:

As you can see in the above code snippet, a piece of executable code is being added to the strategies array which was initialized in the beginning. After the template file runs its bundle install command, we invoke a method called execute_strategies that will loop through the lambdas originating from different recipes and call each of them in turn.

defexecute_stategiesstategies.each{|stategy|stategy.call}end

Using RVM

I love RVM (Ruby Version Manager). For each Ruby version installed, RVM allows a specific groups of gems (Gemsets) to be associated with a project. This is a highly useful feature as it ensures that the gems of one application are isolated from those of another.

To utilize the Gemset feature, these are the commands you need to run on the console before actually generating the Rails project: