Roda is my favourite Ruby Framework, but one thing that initially vexxed me was route file structuring and respecting the powerful routing tree.

Some of my early Roda apps had pretty heavy route files which makes them less pleasant to work in.

More recently I’ve developed a better understanding of both the hash_branches and hash_branch_view_subdir plugins. They both do very similar things, but the latter adds a bit of magic to have your views rendered out of a matching folder structure.

TL;DR you can grab a basic setup from this Github repo if you know what you're doing.

Roda vs Rails Routing

Admittedly, I don’t fully understand the power of the Roda routing tree but, even still, I prefer it to the Rails way.

Rails offers a lot of convention over configuration, but peversely (to me) requires you to explicitly declare routing to controller actions (beyond the standard REST route conventions).

Roda lets you define routing via the DSL directly which I prefer (though this has some disadvantages when working in an IDE or with symbol navigation).

Route splitting

Once an application gets to a certain level of complexity (or even before!), splitting routes into separate files makes a lot of sense.

There is helpful advice in the docs about structuring large applications, but I wanted to explicitly illustrate an approach to how you can split the routes up.

The concept is also detailed in the Mastering Roda book, but I find it helpful to write it all out explicitly for my own understanding.

In our basic example, we’ll create a very basic application with the following file structure.

.
├── Gemfile
├── Gemfile.lock
├── config.ru
└── routes
    └── api
        ├── v1.rb
        └── v2
            ├── teams.rb
            └── users.rb

You can get most of the way by running…

bundle init && bundle add roda rackup
mkdir -p routes/api/v2
touch config.ru
touch routes/api/v1.rb
touch routes/api/v2/teams.rb
touch routes/api/v2/users.rb

…which will get the 2 gems installed we need and set up the couple of files to demonstrate route file splitting.

Application Structure

We’ll make use of the hash_branch plugin along with the empty_root plugin in our application and declare an App class in config.ru:

# config.ru
require 'bundler'
Bundler.require

class App < Roda
  plugin :hash_branches
  plugin :empty_root

  # Use this one if you're going to render views
  # plugin :hash_branch_view_subdir

  Dir['routes/**/*.rb'].each { |file| require_relative file }

  hash_branch('api') do |r|
    r.hash_branches('api')
  end

  hash_branch('api', 'v2') do |r|
    r.hash_branches('v2')
  end

  route do |r|
    r.hash_branches
    'Hello World!'
  end
end

run App

What’s happening here? We’re importing a few files - v1.rb will be a basic route and the v2/ routes will be further split.

We’re telling Roda to use hash_branches on the /api route request and then additionally on the /api/v2 route request. Roda is going to look for a matching hash_branch block in the imported route files to find out where to dispatch to (layman’s explanation).

Simple Route

So v1.rb is going to contain all our routes (“actions”) for requests to the /api/v1 path:

# routes/v1.rb
class App
  plugin :json, classes: [Array, Hash]

  hash_branch('api', 'v1') do |r|
    r.on 'users' do
      r.get do
        { response: 'api/v1/users' }
      end
    end
    
    r.root do
      { response: 'api/v1' }
    end
  end
end

Ths will render JSON objects for the different paths (r.root matches against /api/v1). The users branch shows how you can nest another branch and return a response for /api/v1/users). This might be good for a small application.

More complex route

Syntactically our two v2 files are very similar to our v1 file, but each file will handle a sub-branch only as we declared in config.ru that we want to branch on /api/v2 as well.

Hopefully this demonstrates how you could put a single REST resource in a single file instead of having one route file with all your resources.

# routes/api/v2/teams.rb
class App
  plugin :json, classes: [Array, Hash]

  hash_branch('v2', 'teams') do |r|
    r.root do
      {response: 'api/v2/teams'}
    end
  end
end

…and then a second branch for completeness…

# routes/api/v2/users.rb
class App
  plugin :json, classes: [Array, Hash]

  hash_branch('v2', 'users') do |r|
    r.root do
      {response: 'api/v2/users'}
    end
  end
end

I’ve personally found this a nice way to manage routes over files. As with most things engineering it comes down to pragmatism and preferences (either personal or team). Hopefully this gives a bit of insight as to how you might manage a larger Roda project.

If you run into any issues, the Roda Github Discussions is the best first port of call.

TL;DR you can grab a basic setup from this Github repo if you know what you're doing.