This is Anti-pattern—thoughts on programming and whatnot by Brandon Weiss.

Complex Static Sites on Heroku

May 14th, 2015

A few years ago I wrote about a simple way to host static sites on Heroku. That was great when my personal site was just one page and some assets, but it eventually grew past that. Now it’s a handful of static pages built by Middleman, as is this blog.

Hosting a static site generated by Middleman (or Jekyll) on Heroku is easier than it might seem. First you generate the static files, then you serve them. You don’t need a custom buildpack or a gem, all you need is Rake and a few middlewares.

Generating

Do not build your site locally and commit the build folder to your repository before deploying. That is gross and unnecessary.

Heroku’s Ruby buildpack will invoke a Rake task called precompile:assets during deployment if it exists. This feature is primarily for deploying Rails apps, but you can hook into it by creating your own precompile task that will build your static site during deployment.1

# Rakefile
require "rake"

namespace :assets do

  desc "Precompile assets"
  task :precompile do
    Rake::Task["assets:clean"].invoke
    sh "bundle exec middleman build"
  end

  desc "Remove compiled assets"
  task :clean do
    sh "rm -rf #{File.dirname(__FILE__)}/build/*"
  end

end

Serving

Serving a static site is usually dead simple. Rack::Static, the same middleware that serves assets like images and stylesheets can be used to serve any static file. Unless, like me, you’re very particular about your URLs and don’t want them to have file extensions.

If you want pretty URLs you’ll have to generate static files that are Directory Indexes-compliant.2 Middleman has a Directory Indexes extension and Jekyll has a pretty permalink style. Then you can use the Rack::TryStatic middleware in rack-contrib to map your URLs to the right files on disk.

# config.ru
require "bundler"
Bundler.setup("production")

require "rack/contrib/try_static"
require "rack/contrib/not_found"

use Rack::TryStatic, {
  root: "build",
  urls: %w[/],
  try:  %w[
    .html index.html /index.html
    .xml  index.xml  /index.xml
  ]
}

run Rack::NotFound.new("build/404/index.html")

Rack::TryStatic is just like Rack::Static except it will sequentially try appending each postfix in try to the URL to check if there’s a matching file on disk. If it finds a match it will serve it via Rack::Static, if not it will pass on through and serve the 404 page.

That’s it!


  1. These are the shell commands for Middleman, but if you’re using Jekyll or something else you’ll need to adjust them. 

  2. Back in the day, most HTTP servers would respond to a request for a directory (a URL with a trailing slash) by listing the directory’s contents. They also commonly had a feature called Directory Indexes that would serve an index.html file found inside the directory instead. So if you went to /foobar/ it would serve a file at /foobar/index.html. Everyone realized they could make their URLs prettier this way if they just re-jiggered their file structure a bit. This is why for a long time trailing slashes were so common in URLs. Eventually most static sites were replaced by something dynamic like a content management system or some other application. These systems look at a URL and decide what content to serve. At that point it became pretty trivial to drop the trailing slash and map /foobar onto some page with a title of “Foobar” in the database. That’s why you almost never see file extensions or trailing slashes in URLs any more; the file extension is usually implied and unnecessary, and the trailing slash was only ever a hack we used to get rid of them in the first place. 

Transactional Fixtures in Rails

March 18th, 2015

If you use Ruby and write automated tests you’re probably familiar with Database Cleaner. It’s a gem for cleaning your database in between each test. What you may not know is that if you use Rails, using Database Cleaner is entirely unnecessary because of transactional fixtures.

Transactional fixtures are a great, but not particularly well-known feature of Rails. They are turned on by default and only need to be turned off in rare cases, so some might be aware of the feature, but not necessarily the name for it. And the name is also perhaps a bit misleading, as it technically has nothing to do with fixtures.

Transactional fixtures simply wrap each test in a database transaction and start a rollback at the end of it, reverting the database back to the state it was in before the test. If the database was empty before the test, it will roll it back and be empty after the test. If the database had preloaded test data (like fixtures) in it before the test, it will roll it back and have that same data after the test. Even though this feature is unrelated to fixtures themselves, fixtures are the default way to create test data in Rails, hence the name “transactional fixtures”.

If you’re thinking transactional fixtures sounds almost exactly like what Database Cleaner does, you would be correct. Database Cleaner is entirely unnecessary in a default Rails app. I’ve been using Rails pretty much since the day it came out and had absolutely no idea it had this feature.

I only figured it out while debugging an issue with a transaction I was testing. I turned off Database Cleaner to try and narrow down the problem and noticed the test database was still empty. Somehow the database was still being cleaned after every test.

I started wondering if Rails 4.x added this feature recently and I hadn’t noticed. I cloned down the Rails repo, found the relevant code, and started looping backwards through the commit history to find the commit where transactional fixtures were added.

Eventually I found it, back in 2005. That’s the year Rails was released; it’s had database cleaning from the very beginning. I’ve been running Database Cleaner unnecessarily on Rails apps for like four years now.

Database Cleaner’s README even says “One of my motivations for writing this library was to have an easy way to turn on what Rails calls “transactional_fixtures” in my non-rails ActiveRecord projects”. Epic facepalm.

I think what happened is that I didn’t really start testing until a few years after I discovered Ruby and Rails. This would’ve roughly coincided with the Rails 2.3 era, when I experimented with using lighter-weight frameworks like Sinatra, as well as other databases and ORMs, like MongoDB and Sequel. I started using Database Cleaner because it was necessary. When I eventually came back to Rails years later, I just continued using Database Cleaner because I’d always used it, and I never bothered to read the fucking manual.

Nothing is more motivating than feeling like an idiot, so I submitted a pull request to Rails a week ago to give the feature a better, clearer name, and it was just recently merged! When Rails 5.0 ships the new name for “transactional fixtures” will be “transactional tests”.

Railyard

February 24th, 2015

I don’t always install the Rails gem globally, but when I do, I cry myself to sleep at night. —Me

Ruby gems fall into one of two categories based on how you install them. There are the gems you install globally, outside the context of an application, like Bundler or Pry. And then there are the gems you install locally, inside the context of an application, like Faraday or BCrypt. But then there’s Rails.

Rails is an application dependency so it should be installed locally with Bundler. But the Rails gem is also used to generate the skeleton of the application, which includes the Gemfile that Bundler uses in order to install Rails locally. It’s a bit chicken and the egg.

You’re probably wondering why that even matters. You just install the Rails gem globally.

$ gem install rails
Fetching: thread_safe-0.3.4.gem (100%)
Successfully installed thread_safe-0.3.4
Fetching: minitest-5.4.2.gem (100%)
Successfully installed minitest-5.4.2
Fetching: tzinfo-1.2.2.gem (100%)
Successfully installed tzinfo-1.2.2
Fetching: i18n-0.7.0.beta1.gem (100%)
Successfully installed i18n-0.7.0.beta1
Fetching: activesupport-4.1.6.gem (100%)
Successfully installed activesupport-4.1.6
Fetching: erubis-2.7.0.gem (100%)
Successfully installed erubis-2.7.0
Fetching: builder-3.2.2.gem (100%)
Successfully installed builder-3.2.2
Fetching: actionview-4.1.6.gem (100%)
Successfully installed actionview-4.1.6
Fetching: rack-1.5.2.gem (100%)
Successfully installed rack-1.5.2
Fetching: rack-test-0.6.2.gem (100%)
Successfully installed rack-test-0.6.2
Fetching: actionpack-4.1.6.gem (100%)
Successfully installed actionpack-4.1.6
Fetching: activemodel-4.1.6.gem (100%)
Successfully installed activemodel-4.1.6
Fetching: arel-5.0.1.20140414130214.gem (100%)
Successfully installed arel-5.0.1.20140414130214
Fetching: activerecord-4.1.6.gem (100%)
Successfully installed activerecord-4.1.6
Fetching: mime-types-2.4.3.gem (100%)
Successfully installed mime-types-2.4.3
Fetching: mail-2.6.1.gem (100%)
Successfully installed mail-2.6.1
Fetching: actionmailer-4.1.6.gem (100%)
Successfully installed actionmailer-4.1.6
Fetching: thor-0.19.1.gem (100%)
Successfully installed thor-0.19.1
Fetching: railties-4.1.6.gem (100%)
Successfully installed railties-4.1.6
Fetching: sprockets-3.0.0.beta.2.gem (100%)
Successfully installed sprockets-3.0.0.beta.2
Fetching: sprockets-rails-2.2.0.gem (100%)
Successfully installed sprockets-rails-2.2.0
Fetching: rails-4.1.6.gem (100%)
Successfully installed rails-4.1.6
22 gems installed

Because Rails has a ton of dependencies and installing it globally makes an absolute mess of your gem list. It completely obscures any relevant information you might be trying to find in there. And good luck uninstalling it; you’re going to have to manually uninstall each dependency.

I realize this is definitely a nit, but it bothers me to no end. You don’t need Rails globally except to generate new app skeletons.

I thought I’d be clever and install Rails with --ignore-dependencies, but it turns out the Rails binary isn’t even in the Rails gem, it’s in the Railties gem. I tried installing the Railties gem without dependencies, but it turns out the logic for generating a Rails skeleton is spread throughout a bunch of dependencies.

So I whipped up a simple gem called Railyard. It sandboxes Rails, installing it locally inside the gem, on demand. You can use it to switch to any Rails version you like and generate a Rails skeleton for it, without having to install Rails globally.

$ gem install railyard
Fetching: thor-0.19.1.gem (100%)
Successfully installed thor-0.19.1
Fetching: railyard-0.1.0.gem (100%)
Successfully installed railyard-0.1.0
2 gems installed
$ railyard new next_big_thing

Ah, that’s so much better.