Recently, I needed to create a small, temporary web application for testing purposes. I definitely didn't want to deal with Rails for this purpose. In fact, it was small enough that I just wanted to have everything in one file. So:

  • Sinatra for the framework ✅
  • But what about templates?

Wouldn't it be nice if I could just define the DOM in Ruby? Then I remembered that ActiveAdmin has something like that. I wondered if perhaps there was some gem that they used for defining the DOM in Ruby. It turns out it was part of ActiveAdmin, but the authors extracted that functionality into its own gem, Arbre. Sweet!

Using these two gems together, a dynamic Ruby web application is as easy as this:

require 'sinatra'
require 'arbre'

get '/' do
  Arbre::Context.new do
    html do
      body(style: 'background-color: #DDF') do
        h2 "What time is it?"
        para do
          "It is #{Time.now}"
        end
      end
    end
  end
end

Most of the "tag" methods are just what you'd exepct, h1, a, img, etc. They made the paragraph tag para because p would be confusing as it is usually an alias for puts. Here's our application:

Time web application

That's pretty exciting, right? But can we do more? Sure we can!

Arbre Components

You can do most of what you want just using the tag methods. But let's say you have some common pattern. For example, there's no radio_button method, because that's really just an input tag with the type radio. But if you want a few of them, and you want them all to have labels, creating a component is handy, and super easy. You just extend Arbre::Component like this:

class Radio < Arbre::Component
  builder_method :radio
  def build(attributes = {})
    super(attributes)
    label do
      input(type: :radio, name: attributes[:name], value: attributes[:value])
      text_node attributes[:label]
    end
  end
end

Putting it together

We can use our new component and an additional Sinatra action to create this industry disrupting little beauty:

require 'sinatra'
require 'arbre'

get '/' do
  Arbre::Context.new do
    html do
      body(style: 'background-color: #DDF') do
        h2 "What flavor would you like?"
        form(method: :post) do
          radio(name: :choice, value: :vanilla, label: 'vanilla')
          radio(name: :choice, value: :chocolate, label: 'chocolate')
          radio(name: :choice, value: :strawberry, label: 'strawberry')
          br
          input(type: :submit)
        end
      end
    end
  end
end

post '/' do
  choice = params[:choice]
  Arbre::Context.new do
    html do
      body(style: 'background-color: #DDF') do
        h2 "Good choice"
        para "I like #{choice}, too!"
      end
    end
  end
end

class Radio < Arbre::Component
  builder_method :radio
  def build(attributes = {})
    super(attributes)
    label do
      input(type: :radio, name: attributes[:name], value: attributes[:value])
      text_node attributes[:label]
    end
  end
end

And here's our application:

Ice cream web application

Let's choose chocolate!

Ice cream web application - chose chocolate radio button

Nice work!

Ice cream web application - says good choice!

That's it!

Great solution for building truly ambitious web applications? No. But for something small and quick, it is does the trick, and it is fairly elegant. It doesn't feel all that much different from writing HAML.