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.
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.