Thursday, January 24, 2013

We Can Solve The Multiple-"Default"-Stacks Problem With Rails Application Templates

Steve Klabnik blogged that Rails has two default stacks: the official default stack Rails ships with, and the de facto default stack, used by most prominent Rails developers.

First, let’s talk about the actual default stack. Since Rails is omakase, I’m going to call this the ‘Omakase Stack.’ Roughly, this stack contains:

ERB for view templates
MySQL for databases
MiniTest for testing
Fat Models, Skinny Controllers

There has been a ‘second default stack’ brewing for a while now. I’m going to call this the “Prime stack”. This stack’s approximate choices are:

Haml for view templates
PostgreSQL for databases
Rspec/Cucumber for testing
Skinny models, controllers, and a service layer

But his metaphor breaks down a little, because the second stack isn't really a stack:

A considerable minority uses a stack like this. It’s important that the Prime Stack isn’t exact: you might not use Cucumber at all, for example, or maybe you don’t have a service layer.

On the (private, but cheap) Ruby Rogues "Parley" email list, I came up with an alternate interpretation:

I think the problem is the concept of a stack.

Everybody builds Rails apps their own way. There's a 37Signals (or
omakase) stack, a Thunderbolt stack, a Hashrocket stack, and many
other stacks, and in most cases the "stack" is not a fixed machine but
a fluctuating ecosystem. (I'm not sure if that's the right metaphor
either, but it'll work until I get an idea for a better one.) You
experiment with different gems on different projects, and some of them
you use more often than others.

There's an approved set of choices which represents Official Rails™,
but this doesn't have much to do with any actual consensus among Rails
developers. A lot of people depart from the canonical path a little
bit, for many different reasons. But Rails developers know the value
of convention over configuration, so we all try to develop
*conventions* for our deviations from the canonical path.

For example, "we use DataMapper on every project" becomes a local
convention at Company XYZ. But prior to Rails 3, you maybe had to
write a shell script or something to strip out ActiveRecord and swap
in DataMapper, so if you're doing this, and you do a lot of
consulting, you might start to accrue a little library of post-
installation scripts.

If this continues for a long time, and your company develops a
sufficiently sophisticated set of consistent deviations from Rails
canon, it's almost like you have an alternate version of Rails.

In my opinion the alternative "stack" is really an intersection of
many alternate versions of Rails. Within the context of companies
(i.e., semi-private communities), we've created a huge range of custom
Rails permutations, and people share a vague consensus about certain
consistent ways these individual branches differ from the official,
canonical stem.

(Trying to think of the right metaphor but nothing springs to mind.)

Anyway, I think this is a good thing, but I think the Rails docs would
be better if they acknowledged this process, and its artifacts, and if
we had ways to sharpen the focus and turn the vague consensus into
something more specific.

But I was wrong. Rails has supported this use case for many years, and the docs for the upcoming Rails 4 release describe it in detail.

Rails's creator David Heinemeier Hansson has a Twitter feed. The bad news: I find that feed kind of noisy. The good news: I found a good tweet in it today, and I think it deserves some attention.

Rails application templates provide a simple DSL for creating local, custom Rails "defaults." We've had this feature since Rails 2.

Caveat: in some cases, the DSL gets a little silly:

That's a custom command, in this DSL, for running arbitrary git commands. I might put any git commands in a shell script instead, but even if you opted for a Unix-centric approach, you can still trigger shell scripts (or indeed any arbitrary Unix software) from within a Rails app template via the run command, which allows you to execute arbitrary Unix commands.

Templates operate as command-line arguments to rails new, either as filenames or URLs, e.g.:

$ rails new my_application_name -m ~/my_company_defaults.rb
$ rails new my_application_name -m

Which would be somewhat equivalent to running this manually:

rails new my_application_name && ruby ~/bin/my_company_rails_customizer.rb

...but with a much nicer syntax.

Irony time: the use case for which Hansson recommends using Rails application templates is not actually a use case which the Rails app templates DSL supports.

Many of the least popular changes to Rails have revolved around very small changes to the standard Gemfile -- typically a matter of removing a few lines when you first generate your app -- but the Rails app templates DSL does not have a remove command. It does have a gem command for adding gems, but if you're removing them, you'll be using custom code in either Ruby or some other language.

However, this is easy in Ruby, and extremely easy in bash.

Say you've got some Rails Gemfile which includes both an awesome gem and a lame one:

[01-24 10:55] ~/dev/example_project
↪ cat Gemfile
gem 'awesome'
gem 'lame'

Removing the lame gem is a one-liner in bash:

[01-24 10:55] ~/dev/example_project
↪ grep -v "lame" Gemfile > && mv Gemfile

Problem solved.

[01-24 10:55] ~/dev/example_project
↪ cat Gemfile
gem 'awesome'

With the run command, that is of course also a one-liner in a Rails application template:

run "grep -v 'lame' Gemfile > && mv Gemfile"

Admittedly, it's a long line, and you have to be decent at Unix to make any sense of it, but it's still better than doing it by hand or arguing about it on the Internet. By the way, if you find a cleaner way to do it in bash, please gist me a solution (@gilesgoatboy on Twitter) as I'm no bash wizard. I hope it's obvious you can wrap that in a method:

def no_fugu_please(toxin, dish)
  unix = "grep -v '#{toxin}' #{dish} > #{dish}.new && " +
         "mv #{dish}.new #{dish}"
  run unix

Now you have a usable remove:

no_fugu_please("some_lame_gem", "Gemfile")
no_fugu_please("/bin", ".gitignore")

If you don't want to litter your code with obscure sushi jokes, you could even just use remove for your method name:

remove("some_lame_gem", "Gemfile")
remove("/bin", ".gitignore")

It's important to realize, however, that this does not solve the fundamental problem, on its own, of Rails having two "default stacks." The only way for the community to solve that problem is to both use Rails app templates and share them on GitHub.

Apart from anything else, if you can count the number of Rails app templates which throw away one gem and replace it with another, you now have an objective metric for what techniques the Rails community favors at any given time, as well as a subjective metric ("who uses what") for the credibility of individual gems and tweaks.

So please, use Rails application templates, and share them on GitHub.

Update: improved Unix-fu from Anthony Moralez and more from a Zander.

Also a GNU sed version and old generator web sites.