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:
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:
Let's choose chocolate!
Nice work!
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.