Godwin Ekuma I learn so that I can solve problems.

Roda, the routing tree web toolkit: A tutorial

6 min read 1735

Roda is a Ruby web framework created to bring the best of Sinatra and Cuba into Ruby web development. Roda was designed around the concept of a “routing tree.”

In web development, routing is the process of taking a request, then finding the code that would handle that request. Consider an HTTP request path like the one below.

GET users/1/invoices

When deciding how to handle a route, a typical web framework (e.g., Ruby on Rails or Sinatra) looks at the full request path, iterating an array of possible routes to match the request.

On the other hand, a routing tree framework such as Roda checks each segment of the request path before continuing.

In handling the request above, Roda looks at the first segment, /users. If this segment does not match the request, it skips the entire /users branch so that the other routes under /users are not considered. If /users matches, it is going to look into the /users branch for /1. It continues this way until the route is found, or otherwise returns a 404(not found) status if the route cannot be matched.

Why Roda?

Performance

Since route handling does not involve iterating through an array of routes, per-request overhead is significantly lower when compared to other Ruby frameworks.

A graph comparing Roda's ability to serve more requests while using less memory as compared to other Ruby frameworks
Source: Roda website

Usability

What makes a routing tree interesting is not just performance, but that at any point during routing, you can operate on the current request.

Simplicity

Roda is designed to be simple, both internally and externally. It uses a routing tree to enable you to write simpler and DRYer code.

Reliability

Roda has very little global namespace pollution. All instance variables used internally in the routing tree are prefixed with an underscore (@_variable), so they do not conflict with the instance variables the user wants to use. Additionally, Roda prefixes constants with Roda:: (Roda::constant) to avoid conflicting with the constants that you use for your application.

Extensibility

Roda is extensible via broad plugin support. You can override any part of Roda and call super to get the default behavior.

A simple routing tree

Let’s start off by creating a very small web application to understand what working with Roda looks like. Roda is more of a Ruby library than a framework, and does not have CLI utility commands for performing regular tasks. So unlike Rails and other frameworks, it does not have commands for creating a new project, generating models, controllers, and other command-repetitive tasks.

Let’s create a directory to hold our project files.



mkdir my_roda_app

Then we add a Gemfile for managing the gems we will use. Add the roda gem into the file, and then puma, which will be our web application server. Our Gemfile will look like this.

source "https://rubygems.org"

gem "roda"
gem "puma"

Now we run bundle install to install the gems we’ve added.

bundle install

Most Ruby web frameworks were built on top of Rack, which, according to Rack’s documentation, is a modular Ruby web server interface.

Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.

Rack documentation

Roda is Rack-compatible, so we start out by creating a rackup file using the standard filename config.ru. In it we require roda, and then create a new class to represent our application. This app will inherit from the Roda class.

Because Roda is built on top of Rack, every class that inherits from Roda implicitly becomes a Rack application. In order to tell Rack (and the web server) to execute our app for HTTP requests, we have to tell it to run the App class.

require "roda"

class App < Roda
  route do |r|
   "Hello World!"
  end
end

run App

Then from the command line, we run the rackup command to start up the web server and begin serving requests.

rackup

Open the browser and navigate to http://127.0.0.1:9292 to see “Hello world!”

Our app at its current state returns “Hello world!” as the response for every request route. Let’s introduce Roda’s routing method to make the app return the same response, but for only a specific request path and method.

Roda routing tree methods

r.on method

The first routing tree method is r.on, which creates branches in the routing tree. Let’s add r.on to our example app.


More great articles from LogRocket:


require "roda"

class App < Roda
  route do |r|
    r.on 'users' do
     "Hello Users!"
    end
  end
end

run App

We called r.on with the string users, which will match the current request path if the the request path starts with users.

The Roda.route method is the start of the routing tree. All requests coming into our application will be yielded into the block passed into Roda.route. The block is yielded a route request instance with some additional methods. Conventionally, the block argument is named r (e.g., |r|). The additional methods (e.g., r.on ) passed to the route request instance are used for routing the request.

Route methods accept arguments called matchers, which are used to match the current request. In our example above, we passed a string matcher users as an argument to r.on to create the users branch.

In Roda, string matchers usually match the first segment of the request path. So if the request path starts with users, it matches and is redirected to the passed-in block, which returns the “Hello Users!” string that Roda is using as the response body.

If the request path starts with profile (which does not match), r.on returns nil without yielding to the block, and execution would continue after the call. If no other block exists, which is the case in our example above, Roda.route will return nil and Roda will return a 404 status code with an empty response body.

r.is method

r.on will match all request paths starting with users, including users/non-existent-user. Usually, this is not what we want. Instead, we want to return a 404 response for any path that has not been specifically handled.

This is where r.is comes in handy. It only matches a path if all of the arguments match and there are no further entries in the path after matching. In other words, it finalizes the routing path.

r.is will only match users and not users/non-existent-user. Routing in Roda is done using a combination of the r.on and r.is methods. r.on does prefix matching of the request path, and r.is does full matching of the request path. So while r.on creates branches, r.is creates leaf nodes.

Let’s add a r.on to our example.

require "roda"

class App < Roda
  route do |r|
    r.on 'users' do
      r.is "list" do
       "Hello Users!"
      end
    end
  end
end

run App

Our app will match any request with the path /users/list. r.on creates a branch that handles all paths under users, and r.is creates a node that matches only if the current path is list. r.is is able to match list because the request path is modified as the request is being routed.

When the request for users/list comes in, the routing tree uses the initial request path users. When the r.on "users" request method matches, it consumes users from the front of the request path. Inside the r.on block, the request path is empty.

In the next r.is block, the current path is list and will only match if all of its matchers agree throughout the request path. So in this example case, the request will be matched and “Hello Users!” will be returned.

r.get

Routing will usually take the request method into account as well. While r.on and r.is focus on routing using the request path, r.get is for routing based on the GET request method. If it is invoked without a matcher, it puts a simple match against the request method. If invoked with a matcher, a terminal match is performed against the request path.

require "roda"

class App < Roda
  route do |r|
    r.on 'users' do
      r.is "list" do
        r.get do
         "Hello Users!"
        end
      end
    end
  end
end

run App

A GET users/list request would return “Hello Users!”

r.post

While r.get matches GET requests, r.post matches POST requests.

require "roda"

class App < Roda
  route do |r|
    r.on 'users' do
      r.is "list" do
        r.get do
         "Hello Users!"
        end
        r.post do
         "User added"
        end
      end
    end
  end
end

run App

A POST users/list request would return “User added.” r.get and r.post are used to handle various request methods on similar request paths.

Code execution during routing

Roda has the ability to execute code during the routing process. This makes it easy to write simpler and DRYer code and avoid code duplication.

If you want to check whether someone is logged in before accessing a route, you can put the code that checks for login as the first line in the Roda.route block. This is useful when dealing with separate request methods for the same request path. For instance, a GET request will find a user and return their details, and a POST request will find a user and update their details.

We can abstract the logic for finding the user to a routing block shared by both request methods as shown below.

class App < Roda
  route do |r|
    require_login!
    r.on 'users' do
      r.is ":id" do |user_id|
        @user = User.find(user_id)
        r.get do
         @user
        end
        r.post do
         @user.update(params[:user])
        end
      end
    end
  end
end

run App

Conclusion

Roda is designed to be lightweight and ships only with essential features to get your app quickly running. All additional features are loaded via plugins; Roda ships with over 60 built-in plugins, all of which are maintained with the same level of quality as Roda itself, so you’ll rarely need external ones.

Credits

Jeremy Evans on Github
RubyConf 2014 – Roda: The Routing Tree Web Framework by Jeremy Evans
Mastering Roda

Get setup with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Godwin Ekuma I learn so that I can solve problems.

Leave a Reply