Ruby is an amazingly expressive scripting language that has gained, largely due to Ruby on Rails, a huge following and developer ecosystem. So much so that a lot of people struggle to detangle Ruby (the programming language) from Rails (the framework). But for the new, or even seasoned, Ruby developer there’s a healthy market of non-Rails frameworks out there.
Having built web apps in Ramaze, Sinatra, Grape (API centric), Rails I now continue to be blown away - after 2 years - by the relative newcomer that is Roda. Originally forked from the Cuba microframework, Roda has been nurtured and furthered by Jeremy Evans to become a beautifully powerful web or API framework. If that name sounds familiar, he is also the maintainer of the Sequel ORM and numerous other highly visible Ruby and OpenBSD projects.
Why Roda
This post, and subsequent ones, isn’t about Roda vs. Rails/Sinatra/Hanami. It’s about why you might want to choose to use Roda, and helping you on that journey. Most mature Ruby web frameworks can be used to build APIs/CRUDs/CMSs and it’s very unlikely your users care one bit about your framework assuming the thing keeps running.
Roda is a great choice if any of these reasons resonate:
- Start with a small, compact core of an application and grow complexity over time and learning as you go
- Have fine grained control on features and performance
- Use a routing-tree approach to represent your app instead of being persuaded towards
index/view/edit
controller routes - You don’t want to be overwhelemed with generator/boiler-plate code
Again, this isn’t a better-or-worse comment on Ruby web frameworks, but rather a journey into the fast and powerful Roda!
Your First Roda App
To get started we’re going to create a super simple app with the following directories and files:
1-first-app
├── Gemfile
├── app.rb
├── config.ru
├── routes
│ └── first-route.rb
└── views
├── index.erb
└── layout.erb
You can copy and paste the lines below to re-create this structure:
mkdir 1-first-app && cd 1-first-app
mkdir views routes
touch Gemfile app.rb config.ru routes/first-route.rb views/layout.erb views/index.erb
It’s possible to create a one-file Roda app (config.ru
) but this isn’t representative of an app you’d grow over time!
With the above structure created, open up Gemfile
in your editor of choice and update it to match per below:
I’ve added comments, but to briefly summarise - roda
is the web framework, tilt
is a generic interface to work with multiple template languages/syntaxes, erubi
is an ERB implementation by Jeremy Evans (ERB is the defacto ruby template language supported in Ruby’s standard library), puma
is the web application server (what Apache or Nginx would talk to) and rack-unreloader
reloads modified code so you can refresh the browser and see code changes without having to cycle your app.
Once you’ve installed your gems with bundle install
, open up app.rb
so we can create the core of this first, basic app.
Setting up the Roda App
Update your app.rb
file to look like the below:
So, what’s happening here? We’re greating a Class that inherits from Roda - this App
class is what we’re going to later run via config.ru
to serve the application.
You can see we’re loading a few plugins to introduce optional functionality to the app. As mentioned early, Roda allows you to build powerful and complex apps, but most functionality gets loaded via plugins to keep things lean.
The render
plugin is Roda’s interface to the tilt
library, and is responsible for rendering views and templates. Note that the plugin defaults to use the layout template defined in views/layout.erb
unless you override it.
The hash_routes
plugin is what we’re using to route URL paths to different blocks (i.e. bits of code). This lets us split our Controller logic into separate files and load them using Unreloader.require('routes') {}
(this loads Ruby files in the routes directory). We’ll see shortly how the controller looks, and routing will start to make more sense.
Finally we set up the “master” route, and tell Roda to use the hash_routes
plugin to route requests. Note that historically you might use the multi_route
plugin to map routes to controller code, but hash_routes
effectively supercedes it and offers more - and faster - routing features.
Roda Route + Controller
Even though Roda could be spun up in a single config.ru
file and run with Rack, it doesn’t really help setup a structure for a real world app. So now you can open up routes/first-route.rb
and update it to match the below:
Here we have a very basic route and controller! The hash_branch
method block goes hand-in-hand with the hash_routes
invocation and tells Roda to use this block to handle web requests that start /first-route
.
With the above block, attempting to access /first-route
would give you a blank page and a 404
response code (we’ll get to proper error pages in a future post). However if you GET access to /first-route/hello
you’ll get the contents of views/index.erb
rendered through views/layout.erb
with a @title
class variable set as “Hello World”.
Remember that the the render
plugin is defaulting to view/layout.erb
. You could override an individual view
method call with:
A basic Layout + View
We’re not overly concerned with presentation here, so open up views/layout.erb
and paste in the following:
The yield
keyword is where the content of your view will go - all fairly standard stuff if you’ve done any web app work before.
Our views/index.erb
is going to be very simple…
We’re pretty much done - we just need to tell Rack (and Puma) how to load and run the application.
Running a Roda App
You can see here that we’re heavily leveraging Unreloader - this watches files and reloads the classes in the app if they’ve changed. It’s kind of like hot reloading, and makes development a much more sane experience. If you’re using other frameworks (e.g. Sequel) you’d pass in an extra class to subclasses
so those classes are also reloaded.
The run
keyword is a rack method call and is used by Puma to actually load and run the app.
Saving the above, then running bundle exec rackup
in your terminal should fire up the app on (by default) port 9292 and you can access the route by heading to http://localhost:9292/first-route/hello
Note that you can also access /first-route/hello/foo/bar/blah
and still get the same result. Why?! Well, our r.get
directive under r.on 'hello'
matches everything starting with /first-route/hello
. If you change r.on 'hello'
to r.is 'hello'
you tell Roda to use an “exact match” on the routing tree and it becomes very strict. The verbs and routing tree take a little getting used to, and you may find it overly fussy moving between on
and is
and may choose to stick to one or the other.
If you’re using the r.is
you probably want to update your plugins to use plugin :slash_path_empty
. This route matching method is an exact match and won’t match if anything is trailing the match. For example:
Given you can’t stop a user typing in a URL, you probably want to avoid sending them to a 404 for the sake of a trailing slash!
A Note on Application Structure
What we’ve built above is obviously very basic, but matches quite similarly to the official conventions for structuring a larger app. One deviation is I prefer to use app.rb
for the application file as it reduces cognitive load when searching and opening the file.
We’re still missing a few key elements that we’ll step through in a future post, though:
- Style and Script assets - Roda has a great
:assets
plugin which can pre-compile and cache Sass and Javascript (and more) - Models and Database connectivity (and migrations)
- Helper modules/classes to help keep our app DRY
- Test files
I’ll be covering up more Roda in a future post!