Mastering Roda

Federico M. Iachetti

Acknowledgments

This book was originally written as a collaboration between myself (Federico) and Avdi Grimm (from RubyTapas)

It is now an Open Source effort maintained by myself and Jeremy Evans (Roda's creator and maintainer).

Creative Commons License
Mastering Roda by Federico Iachetti is licensed under a Creative Commons Attribution 4.0 International License .
Based on a work at https://gitlab.com/fiachetti/mastering-roda .

TODO How to contribute

How to support the project

There are two main ways you can give monetary support to the project.

The first one would be a direct donation through PayPal. Although, you wouldn't get anything in return, except for my eternal gratitude towards you.

The next one would be by purchasing a video course I'm producing using the materials on the book. You could think about it as the "Video version of the book". This course is a little bit outdated at the moment but, since the book will get an update with Jeremy's help, so will the course. You can find it by following this link. You can also get a 20% off by using the coupon mastering-roda on checkout as a thank you for contributing.

The last way to support the project is to share it. More eyes on an open source book means more ideas and much more feedback.

Thank you very much for helping!

Full disclosure
the funds obtained both via direct donation and through product sales go directly to me. I've offered Jeremy to share profits as an affiliate, but he's in no position to accept the deal. So, either if you're planning on contributing or not, you should thank him in any way you can, because it's an awesome effort he's putting into Roda. Thanks Jeremy!

Introduction

I've been a Ruby on Rails developer since 2011. Rails is a great framework. It's also a big and opinionated. It allows you to do almost anything out of the box (whether you need to or not), with little or no extra configuration needed. Unless you want to do something that's "off the rails"… in which case, you're on your own.

After a while using Rails I decided I wanted to move on to smaller frameworks, taking a more minimalistic approach. After some time trying a number of gems I landed on Roda, a small library created by Jeremy Evans, which I really liked. In fact, I liked so much that I wanted to share with you my knowledge to how to use it.

This course is completely driven by examples. Every concept that's introduced is described by providing a problem or situation to solve. Feel free to follow along.

I want to thank you very much for allowing me to share it with you.

About Roda

Roda is a routing tree web toolkit. It is not really a web framework. Rather, it is a small Ruby library for building web applications.

The Roda philosophy prefers simplicity, reliability, extensibility, and performance over conventions or out of the box features. This means that it doesn't enforce conventions or features on us. It rather prefers to ship bare-bones and give us the ability to enable features as required with a very powerful plugins library.

Roda performs routing by implementing what it's creator calls a routing tree. As we'll see in the pages ahead, this routing tree approach is what gives Roda a lot of its flexibiliy and power.

Roda has a lightweight, minimal code-base. This helps it to be a performant library, and also makes it easier to understand.

In developing Roda, Jeremy Evans, has taken special care to avoid polluting the app scope with many instance variables, constants or methods. This makes for less to learn, and helps us avoid unexpected name clashes.

On this book, I'll teach the basic concepts and tools Roda provides, along with conventions and good practices that will help you get started using this amazing library.

But enough chattering, let's try it out.

How to read this book

This book is completely driven by examples. Every concept introduced is described by providing a problem or situation to solve.

You're welcome to simply read this book from cover to cover. But I've written each example in a way that you can follow along at home, and I suggest you do. Applying examples to code is a great way to cement the concepts in your mind.

I'd also suggest that you go further and experiment on the code. If there's something you didn't completely understand, before emailing me, modify the code in order to discover what's missing (and right after that, shoot an email at mailto:info@lucidcode.org. I'll be glad to help you and update the book if needed).

Basics

In this lesson we'll explore Roda's most basic concepts.

We'll learn the basic structure of a Roda application, how it routes requests from the user all the way up to the target code, how to pass data to your application with data and how to handle sessions.

This lesson provides almost everything you need to know in order to create any kind of application using Roda, so hang in tight and get ready for takeoff.

A quick introduction to lucid_http

Before starting with Roda itself, I'd like to introduce a gem I created for showing off HTTP interactions. I won't go into much detail here, but if you want a more detailed explanation, you can jump tho the appendix called "The Lucid HTTP gem".

Lucid HTTP is a thin wrapper over the http.rb library, which provides a very simple and consistent API for preforming HTTP requests. What Lucid HTTP brings to the table is a higher level presentation abstraction.

I created a small app that will allow me to show the gem at work. The code for this app is included in the book bundle, by the name of micro_app.zip. In the appendix I go into detail about how to get it running.

So, we start by making a GET request to the hello path.

To do this, we need to call the GET method, passing the required path as an argument.

require "lucid_http"

GET "/hello"
# => "<h1>Hello World!<h1>"

The method will return the rendered body.

A note about notation

I'm using the seeing_is_believing gem combined with my editor of choice (Emacs) to display the results. The gem will evaluate every line in our code and, on marked lines, display the results.

The way to mark a line to display results is with the # => comment marker. It can go at the end of the line or at the beginning of the next one.

By default, the base URL we're targeting id http://localohost:9292. Notice that there's not a trailing slash on that string, which means that we need to include it on the path we want to request, hence the /hello argument path.

By calling the GET method we're returning the body of our response. But, what about other relevant information? Well, lucid_http also provides the following methods:

require "lucid_http"

GET "/hello/you"
status                          # => 200 OK
status.to_i                     # => 200
content_type                    # => "text/html"
path                            # => "http://localhost:9292/hello/you"

When we decide to make the next request, the current information gets cleaned up, and the new request starts with a clean slate.

require "lucid_http"

GET "/hello/you"
status                          # => 200 OK
content_type                    # => "text/html"
path                            # => "http://localhost:9292/hello/you"
body[/\>(.+)\</, 1]             # => "Hello, You!"

GET "/403"
status                          # => 403 Forbidden
content_type                    # => "text/html"
path                            # => "http://localhost:9292/403"
body                            # => "The request returned a 403 status."

We can follow redirections passing the follow: true attribute

require "lucid_http"

GET "/redirect_me"
status                          # => 302 Found

GET "/redirect_me", follow: true
status                          # => 200 OK
body                            # => "You have arrived here due to a redirection."

If we get an error status (500), we can see what happened by calling error, which will return the first line of the body in order to show a succinct message.

require "lucid_http"

GET "/500"
status                          # => 500 Internal Server Error
error                           # => "SocketError: SocketError"

But if the request doesn't return a 500 code, the library will be nice enough to let us know.

require "lucid_http"

GET "/not_500"
status                          # => 200 OK
error                           # => "No 500 error found."

If we have a json endpoint, the string output might not be the best way to show it

require "lucid_http"

GET "/hello_world"
# => "You said: hello_world"

GET "/hello_world.json"
# => "{\"content\":\"You said: hello_world\",\"keyword\":\"hello_world\",\"timestamp\":\"2016-12-31 15:00:42 -0300\",\"method\":\"GET\",\"status\":200}"

But passing the json: true attribute, we see it as a Hash, which is much nicer to look at. That's better.

require "lucid_http"

GET "/hello_world"
# => "You said: hello_world"

GET "/hello_world.json", json: true
# => {"content"=>"You said: hello_world",
#     "keyword"=>"hello_world",
#     "timestamp"=>"2016-12-31 15:01:06 -0300",
#     "method"=>"GET",
#     "status"=>200}

lucid_http also support a number of other HTTP verbs we can use.

require "lucid_http"

GET     "/verb"                  # => "<GET>"
POST    "/verb"                  # => "<POST>"
PUT     "/verb"                  # => "<PUT>"
PATCH   "/verb"                  # => "<PATCH>"
DELETE  "/verb"                  # => "<DELETE>"
OPTIONS "/verb"                  # => "<OPTIONS>"

Finally, we can send a form to the server using the :form attribute.

require "lucid_http"

POST "/params?item=book", json: true
# => {"item"=>"book"}

POST "/params", json: true, form: { item: "book", quantity: 1, price: 50.0, title: "The complete guide to doing absolutely nothing at all."  }
# => {"item"=>"book",
#     "quantity"=>"1",
#     "price"=>"50.0",
#     "title"=>"The complete guide to doing absolutely nothing at all."}

Now that we have a basic understanding of how results will be displayed throughout the book, we can begin.

A very small hello world

We'll kick things off by creating a very small web application to help get a grasp of what it looks like. Don't expect to become an expert reading this lesson, just lay back and enjoy getting to know Roda.

We first need to create a new Roda project.

If you're used to working in a heavyweight web framework like Rails, you might be expecting to invoking a special command to generate a new project. But Roda is more of a library than a framework.

So our first step is simply to create a new, empty directory.

mkdir my_app

Let's add a Gemfile, to control the Rubygems this project will use.

Naturally, our first gem to add is roda.

Then we need a web application server. We'll go with thin, which is a simple, mature, and fast choice.

One more library I always like to have handy awesome_print. It will make our terminal outputs more readable by providing pretty colors and nicer indentation.

source "https://rubygems.org"

gem "roda"
gem "thin"
gem "awesome_print"

Now we run bundle, to install the gems we've added.

bundle install

For the rest of the book, unless I say otherwise, I'll be using this configuration for every example. The three gems we installed are going to be present in every Gemfile from here on. I suggest you configure your environment the same way if you follow along, in order to avoid headaches.

Now we're ready to start writing some code.

Every major Ruby web application framework is built on top of a universal compatibility layer called Rack. Roda too is Rack-compatible, so we start out by creating a "rackup file", using the standard file name 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.

Roda is built around the idea of a routing tree, which implies creating branches by adding routes. So we start by defining a route block. This block will receive a Request as an argument, which, by convention we abbreviate as r.

Our first route checks to see if the HTTP request is for the /hello path. If so, the block that we provide will be executed. We'll just return the string "hello!" from our block.

By inheriting from the Roda class, our App class is implicitly 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|
    r.is "hello" do
      "Hello!"
    end
  end
end

run App

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

rackup

If we now load http://localhost:9292/hello in our browser, we see our first message. Yay!

Now let's make a change to our tiny application. We'll change the return value of the block.

require "roda"

class App < Roda
  route do |r|
    r.is "hello" do
      "Hello, world!"
    end
  end
end

run App

If we navigate again to http://localhost:9292/hello, we see … nothing has changed. Why not? Because the server is still running our original code.

This is another reminder that we're not building on a fancy web framework. If we want bells and whistles like automatic code reloading, we can have them! … but we have to ask for them.

For a very simple way to reload the code every time we change it, we'll use the rerun gem (if you want a quick intro on the rerun gem, check out RubyTapas Episode #320).

rackup
rerun rackup

Now we can navigate to http://localhost:9292/hello, and see the updated output.

Again, for this moment on assume that, for every example, we'll be running rackup using rerun in order to avoid having to start and stop the server by hand.

For this reason, we'll create our own alias

echo "alias rru='rerun rackup'" << ~/.bashrc
source ~/.bashrc

And we're ready to move on.

We have a working web application, but we'd like to be able to interact with it. Let's give the user the ability to specify who or what is being greeted. For this, let's create a second route. As before, we'll match again the string "hello". But this time, we'll follow the "hello" with a second placeholder argument, called :name. We can tell that it's a placeholder, because it's a symbol instead of a string.

When we specify a placeholder in the string to match, the block we provide will receive a corresponding block argument. Let's give it the same name as the placeholder and then interpolate it into the output.

require "roda"

class App < Roda
  route do |r|
    r.is "hello", :name do |name|
      "<h1>Hello #{name}!</h1>"
    end
  end
end

run App

When we navigate to http://localhost:9292/hello/roda we see that the string Roda is taken from the path and inserted into the page.

So far, we've been working out of the config.ru "rackup file". But a rackup file is really intended just for configuring the app server startup process. Let's organize our app a little better, and move the application code out of the config.ru file.

We remove the entire App class, along with the require statement, and in its place, we require a file named app.rb.

require "./app"

run App

Then we proceed to create the app.rb file and paste the code in.

require "roda"

class App < Roda
  route do |r|
    r.is "hello", :name do |name|
      "<h1>Hello #{name}!<h1>"
    end
  end
end

And everything still works.

And there it is: our very first Roda app. Wasn't that easy?

This is a good one to have at hand.

We are writing a site that tells us about a mystery guest. We don't know what it is, but we should be able to see it by browsing to the /mystery_guest path. So lets

That's not very helpful. What's going on here? If we use LucidHttp to fetch the page, we see that the result is coming back correctly, it's a Pizza!

require "lucid_http"

GET "/mystery_guest"
# => "#<struct Pizza flavor=\"Mozzarella\">"

But why can't we see that on the browser if the response is getting properly returned?

If we take a closer look, everything breaks after the #. The next string is <struct Pizza flavor="Mozzarella">, which a lot like at HTML tag.

What's the solution for this?, well, one solution would be to implement a to_s method in our pizza class and everything would be fine.

But this approach is too naive. Even though for this particular case is the actual solution, it excludes at least two major considerations:

For both cases, the h plugin provides a solution.

After including it, we can pass the string to render to the h method

class App < Roda
  plugin :h

  route do |r|
    r.is 'mystery_guest' do
      h("The Mystery Gest is: #{mystery_guest}")
    end
  end
end

and the response will be properly escaped

require "lucid_http"

GET "/mystery_guest"
# => "The Mystery Gest is: #&lt;struct Pizza flavor=&quot;Mozzarella&quot;&gt;"

I know, it looks pretty ugly but, on the other hand, the result on the browser is much better

Being h an instance method on the app, we can also call it inside our views.

This comes in handy when we already have a view in place and we notice that a part of it needs escaping.

For example, imagine we had the following view in our route

<div>
  The Mystery Gest is:
  <b style="color: green;">
    <%= mystery_guest %>
  </b>
</div>

Which have the same problem as before

And have the same solution, we just need to add a call to the h method

<div>
  The Mystery Gest is:
  <b style="color: green;">
    <%=h mystery_guest %>
  </b>
</div>

And when we reload the browser we have the expected result

I don't usually include this plugin in my projects by default. In fact I don't do it at all unless I actually need escaping in my app. But whenever I encounter a situation like the one I described, I tend to activate it temporarily mostly as a cheap debugging strategy.

Then again, there are some cases when we actually need escaping, and in those cases I don't hesitate for a second.

Basic routing

When you get right down to it, HTTP is just a bunch of requests and responses. And if we only ever needed to respond to one kind of request, we could handle it all with one big hunk of code.

But web applications usually support a lot more than one type of request. And to keep ourselves sane as developers, we usually try to separate out the different bits of code which handle different types of request.

Taking a client's request and directing it to the bit of code that should handle it is called routing and in this section we'll take a look how Roda does it.

As we saw on the examples on the previous section, to create a Roda app first we define a class and inherit it from Roda.

Now, in order to actually add our routes, we go into our Roda app class and call the route class method. We pass a block, which will receive as a parameter an object representing the current request.

Lets stop right here and take a look at what the contents of the r parameter look like. We'll use awesome_print for this. We'll also return an empty string in order to avoid cluttering our output with error messages (we'll explain why this step is necessary later)

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap r
    ""
  end
end

When we browse any path on the app, we see that our block has been passed an App::RodaRequest.

$ rru
Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on localhost:9292, CTRL+C to stop
#<App::RodaRequest GET />                         # <====
127.0.0.1 - - [19/Sep/2016:19:46:26 -0300] "GET / HTTP/1.1" 404 - 0.0016

RodaRequest is just a wrapper around the Rack::Request class.

We can call several methods to help us with the routing on a RodaRequest instance and we're going to explore some of them on the next few sections. But for now, lets start by defining a single route.

We call the on method on our request object, passing it a string parameter and a block. And from the block, we return a string.

class App < Roda
  route do |r|
    r.on "hello" do
      "Hello Lucid!"
    end
  end
end

The on method is what Roda calls a matcher, which is a method that takes the path part of the request, and makes some kind of comparison against it (or matches against it). If the match was successful, the matcher executes the provided code block.

Let's take a look at what it produced.

In order to show client-server interactions, we'll use the lucid_http gem. You can learn more about how to install and use it in the appendix section.

The page content is actually the exact string we returned. Following the Sinatra style, by default,<<return a string from a route>> if we return a string from one of our routes, it gets set as the body of the response and rendered. We can also see that the status code is 200 OK and the response's content type as text/html.

require "lucid_http"

GET "/hello"

body                            # => "Hello Lucid!"
status                          # => "200 OK"
content_type                    # => "text/html"

If we return something other than a plain String,

class App < Roda
  route do |r|
    r.on "hello" do
      :oopsie
    end
  end
end

Roda interprets it as a miss, and returns an empty body, with a 404 Not Found status code.

require "lucid_http"

GET "/hello"

body                      # => ""
status                    # => "404 Not Found"

That's one of those things that takes me by surprise every time it happens. So repeat it in your head several times and let it sink in.

If a route matches but returns ANYTHING OTHER THAN A STRING, Roda will interpret it as a miss and return a 404 status.

Roda doesn't care if it's a nil, a symbol, a number, an array or a pizza, if it's not a String, it's a miss.

class App < Roda
  route do |r|
    r.on "hello" do
      [
        nil,
        :oopsie,
        1_000_000,
        [nil, 2, ?c],
        Pizza.new
      ].sample
    end
  end
end

Note that this is the default behavior provided by the library. In future sections, we'll learn how to very easily change it to suit our purposes.

But the story doesn't end there. Lets go back to our previous example. If we look closely, we see that the actual code that generates the content is wrapped inside a block.

Lets put a debugging statement inside the route block and another one inside the hello block,

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap "ROUTE block"

    r.on "hello" do

      ap "HELLO block"

      "Hello Lucid!"
    end
  end
end

When we browse to the root path of the application (http://localhost:9292/), we can see that the string corresponding to the route block was printed.

And when we browse to http://localhost:9292/hello, both strings are printed.

Note
the server hasn't been restarted between requests.
"ROUTE block"
127.0.0.1 - - [16/Nov/2016:12:56:22 -0300] "GET / HTTP/1.1" 404 - 0.0020
"ROUTE block"
"HELLO block"
127.0.0.1 - - [16/Nov/2016:12:58:35 -0300] "GET /hello HTTP/1.1" 200 5 0.0011

This demonstrates one of the core principles of Roda's design. In many web frameworks, the routing definitions are executed when the program starts, and "compiled" into some kind of internal data structure. But in Roda, the route code is executed "live" every time a request is received.

This is a really powerful idea. For one thing, it makes Roda's routing really easy to understand. You don't have to mentally map the route definition to some kind of internal representation. What you see is what you get: if you can read Ruby code, you can tell what each step of the route will do.

Now, you might be worried that this design could lead to slower and slower routing of requests as the route definitions grow in size. After all, as we just said, Roda is re-executing the definition for every single request. But the real beauty of Roda's design is that only the matching parts of the route definitions are executed! In fact, when it comes to routing, Roda is one of the top performers out there.

As we'll see in future lessons, the incoming request works its way through the Roda routing definition like a caterpillar crawling from a tree's trunk out to the very tip of a twig. Only the parts of the routing code that match the request are ever run.

We can see it happen by adding a second route with debugging statements.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap "ROUTE block"
    r.on "hello" do
      ap "HELLO block"
      "hello"
    end

    r.on "goodbye" do
      ap "GOODBYE block"
      "goodbye"
    end
  end
end

If we now browse to http://localhost:9292/goodbye, we might expect both hello and goodbye strings to appear in the output, but they don't. Only the route that matched was executed, even though that the definition of the hello route came first.

Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on 0.0.0.0:9292, CTRL+C to stop
"ROUTE block"
"GOODBYE block"
127.0.0.1 - - [16/Nov/2016:13:06:14 -0300] "GET /goodbye HTTP/1.1" 200 7 0.0017

In fact, this tree-style execution is why Roda is called a "Routing Tree Web Framework Toolkit".

Another way to think about this is as a lazy evaluation of the routes. A route doesn't get executed unless it's actually needed.

There is another advantage to this tree-ish style of routing. If we need to perform some setup for handling a request, it won't be performed after the routing is done, like other frameworks do, but on the fly, while we are routing. This eliminates the need for so-called "filter" or "hook" mechanisms to perform actions before or after every route.

What do I mean with that? Well, say we have users in our system and both routes either say hello or goodbye to them, but for that to happen, we first need to find the user somehow. We can retrieve it from a database, from the session or any other mechanism we can think of. Lets simulate this by just assigning a local variable. Then we insert it into the output string. Now we need to do the same for the second route.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.on "hello" do
      name = "Lucid"
      "Hello, #{name}!"
    end

    r.on "goodbye" do
      name = "Lucid"
      "Goodbye, #{name}!"
    end
  end
end

If we kept adding sub-routes that need to access this user, we'd need to keep fetching it, right? Well, actually no. Here's when the shared scope nature of code blocks in Ruby pays off in this structure. Instead of retrieving the user each time we need it, we can actually retrieve it once inside the route block and it will be available in all of the nested routes.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    name = "Lucid"

    r.on "hello" do
      "Hello, #{name}!"
    end

    r.on "goodbye" do
      "Goodbye, #{name}!"
    end
  end
end

Now we can browse to http://localhost:9292/hello and we get the expected output. And the same happens if we go to http://localhost:9292/goodbye.

require "lucid_http"

GET "/hello"                    # => "Hello, Lucid!"
GET "/goodbye"                  # => "Goodbye, Lucid!"

All that said, there are some drawbacks to this approach. For example, since Roda does all of its routing on-the-fly when requests are received, there is no easy way to list all the possible routes. If you're accustomed to using commands like rails route -T does to see a static list of an applications routes, this might take some getting used to.

r.on and r.is

In the previous section, we got our first taste of routing in Roda. Now let's explore the different request matchers that Roda provides us out of the box.

In order to do it, we'll begin with a simple example Roda app. Say we're creating a blogging app (very original, right?). We want a /posts route that returns the list of all of our posts.

We create this route using the on method on our request.

We add a variable to hold our data repository which will just be a hash with all of our posts.

From the block, we return a string representing our post list and we just join them using pipe characters for simplicity.

class App < Roda
  route do |r|
    r.on "posts" do
      post_list = {
                    "1" => "Post[1]",
                    "2" => "Post[2]",
                    "3" => "Post[3]",
                    "4" => "Post[4]",
                    "5" => "Post[5]",
                   }

      post_list.values.join(" | ")
    end
  end
end

If we now browse to http://localhost:9292/posts/, we see our posts list.

Notice that we specify the trailing slash at the end of the URL. At this stage lets just use it without asking questions, we'll learn how to make it optional in a future section.

require "lucid_http"

GET "/posts/"
body
# => "Post[1] | Post[2] | Post[3] | Post[4] | Post[5]"

r.on is what Roda calls a non-terminal matcher. A non-terminal matcher doesn't require the path to be completely consumed in order to succeed.

Let's create a couple of them. We want the http://localhost:9292/posts/1/show/ url to return the show page for the post with id of 1. To do so, we nest another call to r.on under the one we have, and, as a parameter, we pass a symbol.

The :id symbol is used as a wildcard. It tells Roda to capture a parameter in that position and pass it to the routing block.

In order to show this off, lets just return the result of inspecting the id parameter.

class App < Roda
  route do |r|
    r.on "posts" do
      post_list = {
        "1" => "Post[1]",
        "2" => "Post[2]",
        "3" => "Post[3]",
        "4" => "Post[4]",
        "5" => "Post[5]",
      }

      r.on :id do |id|
        id.inspect
      end

      post_list.values.map { |post| post }.join(" | ")
    end
  end
end

When we try it out, we see our id. Notice that it is actually passed as a string, it doesn't get parsed to an integer. Is important to remember this in order to avoid headaches in the future.

require "lucid_http"

GET "/posts/1/"

body                            # => "\"1\""

It's also important to notice that we can add multiple segments using symbols for our route by passing them as separate parameters.

For example, if we wanted to show all the posts that where posted on some date, we could capture the year, month and day, create a Date object and pass it to a Post#posts_for_date method but we'd need to explicitly convert them into integers.

# ...

r.on "posts_for", :year, :month, :day do |year, month, day|
  date = Date.new(year.to_i, month.to_i, day.to_i)
  posts = Post.posts_for_date(date)

  # ...
end

#...

Now that we explained that, we can proceed to write our /posts/1/show page. We first put the actual post into a variable, and then we write the route.

We also want a detailed view, where we add the last accessed time. To make this happen, we nest the route under show.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.on "posts" do
      # ...
      r.on :id do |id|
        post = post_list[id]

        r.on "show"  do
          r.on "detail" do
            "Showing #{post} | Last access: #{Time.now.strftime("%H:%M:%S")}"
          end

          "Showing #{post}"
        end
      end
      # ...
    end
  end
end

If we ran this now, it would work, but there's something I don't like about this code: remember in the last section when we said that one of the virtues of the routing tree approach was that it's easy to read? Well, we've now obscured the code by having the contents of the first block after the contents of the second. If we keep adding nested blocks, we'll loose sight of it's contents.

There are a couple of ways of arranging this code. Lets go for the most naive one and just match the show path first, and take care of the show/detail on a separate matcher.

Disclaimer
This more "naive" approach is not the recommended way of doing what is discussed on this section, but it's, in my opinion, a great way to explain it.
require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.on "posts" do
      # ...

      r.on :id do |id|
        post = post_list[id]

        r.on "show"  do
          "Showing #{post}"
        end

        r.on "show/detail" do
          "Showing #{post} | Last access: #{Time.now.strftime('%H:%M:%S')}"
        end
      end

      post_list.values.join(" | ")
    end
  end
end

Now we try it out. We access /posts/1/show, and we get back the expected result. Then we access http://localhost:9292/posts/1/show/detail, but when we execute we find out that we're actually executing the same route as before.

require "lucid_http"

GET "/posts/1/show"
# => "Showing Post[1]"

GET "/posts/1/show/detail"
# => "Showing Post[1]"

What happened here? To understand it lets take a deeper dive into the inner workings of how Roda matches against a request. When a request is made, Roda keeps track of what's been processed by inspecting a series of methods.

The first one is path. If we inspect its contents,

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap r.path
  end
end

we see that it has the full requested path.

Listening on 0.0.0.0:9292, CTRL+C to stop
"/posts/1/show/detail"

Then we have the matched_path.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap r.path
    ap r.matched_path
  end
end

If we look at its contents, we find an empty string. This correspond to the part of the path that has already been consumed or processed by Roda. It's empty because we're printing it at the very beginning of the routing block, where we haven't performed any routing yet.

Listening on 0.0.0.0:9292, CTRL+C to stop
"/posts/1/show/detail"
""

Finally, we have the remaining_path, which, as you've probably guessed, contains the segment of the path that haven't been consumed yet.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap r.path
    ap r.matched_path
    ap r.remaining_path
  end
end

And at this point contains the full path.

Listening on 0.0.0.0:9292, CTRL+C to stop
"/posts/1/show/detail"
""
"/posts/1/show/detail"

Lets put the last two into a hash and inspect it at different points of the process.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    ap matched: r.matched_path, remaining: r.remaining_path
  end
end

Before any processing, we have, as explained above, an empty string as the matched path and the full path as the remaining.

Listening on 0.0.0.0:9292, CTRL+C to stop
{
      :matched => "",
    :remaining => "/posts/1/show/detail"
}

Lets move it now inside the posts block.

class App < Roda
  route do |r|
    r.on "posts" do
      ap matched: r.matched_path, remaining: r.remaining_path
    end
  end
end

Now, we can see that the /posts part of the path has been consumed, remaining /1/show/detail to process.

{
      :matched => "/posts",
    :remaining => "/1/show/detail"
}
127.0.0.1 - - [16/Dec/2016:18:58:31 -0300] "GET /posts/1/show/detail HTTP/1.1" 404 - 0.0009

This means that the path was partially matched. From this we learn that the on matcher consumes the matched segment and leaves the remaining string as the path for the next block to match.

The same happens with the :id segment.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.on "posts" do
      r.on :id do |id|
        ap matched: r.matched_path, remaining: r.remaining_path
      end
    end
  end
end

Now the 1 has been removed from the remaining path and added to the matched path.

Listening on 0.0.0.0:9292, CTRL+C to stop
{
      :matched => "/posts/1",
    :remaining => "/show/detail"
}

OK, now we have a remaining path of /show/detail and two potential routes to transit. What route will it take? Well, the first instinct would be to say that the one that fully matches. So let's inspect that branch.

class App < Roda
  route do |r|
    r.on "posts" do
      r.on :id do |id|
        r.on "show"  do
          # ...
        end

        r.on "show/detail" do
          ap matched: r.matched_path, remaining: r.remaining_path
          "Showing #{post} | Last access: #{Time.now.strftime("%H:%M:%S")}"
        end
      end
    end
  end
end

Nope, nothing was printed, so that means that this branch was never run.

127.0.0.1 - - [16/Nov/2016:21:21:42 -0300] "GET /posts/1/show/detail HTTP/1.1" 200 15 0.0017

But we had a match, we can see that the status was 200.

require "lucid_http"

GET "/posts/1/show/detail"
body                            # => "Showing Post[1]"
status                          # => "200 OK"

Lets explore the other route,

class App < Roda
  route do |r|
    r.on "posts" do
      r.on :id do |id|
        r.on "show"  do
          ap matched: r.matched_path, remaining: r.remaining_path
          "Showing #{post}"
        end
      end
    end
  end
end

and see if it gets run.

Listening on 0.0.0.0:9292, CTRL+C to stop
{
      :matched => "/posts/1/show",
    :remaining => "/detail"
}

Yes, it was! and now we can actually see what happened. The on matcher actually matched against the show part of the path and it was consumed in this route. Once the match was successful, the show branch was invoked and the rest of the tree got ignored.

To solve this, we could just change the order of the blocks in order to force the show/detail path to get matched first. That seems like a reasonable approach.

r.on :id do |id|
  r.on "show/detail" do
    # ...
  end

  r.is "show"  do
    # ...
  end
end

But we'd be letting the library define the order of our routes. We could make an argument that the show route is probably more relevant to our application than the detail route. On top of that, if we grew our application to include similar routes, such as comments, likes, related posts and so on, soon enough we'd find our most relevant route buried under a nest of tangents.

Luckily for us, there is another solution: the is matcher. If we change matchers in the show route.

r.on :id do |id|
  post = post_list[id]

  r.is "show"  do
    ap matched: r.matched_path, remaining: r.remaining_path
    "Showing #{post}"
  end

  r.on "show/detail" do
    "Showing #{post} | Last access: #{Time.now.strftime("%H:%M:%S")}"
  end
end

When re reload the show/detail path, we don't see the debugging statements anymore.

Listening on 0.0.0.0:9292, CTRL+C to stop
127.0.0.1 - - [17/Nov/2016:00:55:57 -0300] "GET /posts/1/show/detail HTTP/1.1" 200 39 0.0017

And that's actually because the show path is not a match anymore. The only way that route will be executed at all is by having the exact path /posts/1/show.

{
      :matched => "/posts/1/show",
    :remaining => ""
}
127.0.0.1 - - [17/Nov/2016:00:59:42 -0300] "GET /posts/1/show HTTP/1.1" 200 15 0.0011

On this example, it's very important to notice that the r.is "show" ... matcher, actually matches the /hello path. The leading slash is important, because Rack actually ensures that the full path begins with a slash.

We can see this by inspecting the r.path method right at the beginning of the route block.

class App < Roda
  route do |r|
    ap r.path
  end
end

And then making a request to http://localhost:9292, without the slash at the end.

require "lucid_http"

GET ""

When we look at the terminal output, we see that the path is not actually empty, but a slash was added.

"/"
127.0.0.1 - - [17/Jan/2017:13:27:57 -0300] "GET / HTTP/1.1" 200 1 0.0008

In most cases Roda expect the remaining path to start with /, unless the path has been fully matched.

is is what Roda calls a terminal matcher. A terminal matcher will execute it's block if and only if the full path was matched. In other words, only if there's no remaining path left to be consumed.

It's important to understand that even though we won't be able to make sub-branches that depend on the path, it's actually possible to make them by other means, that we'll learn in future sections.

As disclaimed before, The example used in this section was chosen for the purpose of explaining the difference between r.on and r.is. But is not actually recommended for development and it's considered an anti-pattern.

The beauty of a routing tree is that it allows us to group related behaviors into branches. By having both show and show/detail branches separated, we're actually making the routing more confusing. Maybe not so much at this very small scale, but it will get more complex as soon as we have to add sub-routes.

We'll learn a way to solve this problem elegantly in the next section.

r.root and r.redirect

So far, we've been looking at how to route requests for various paths. But what about requests that are for the root path of a website?

If you take a look at the previous lesson, you can figure out one of the ways Roda allows us to define it: using the is matcher, with an empty string as it's argument (which will actually match /).

For now we'll just return a dummy string.

class App < Roda
  route do |r|
    r.is "" do
      "Root Path"
    end

    r.is "posts" do
      posts = (1..5).map {|i| "Post #{i+1}"}
      posts.join(" | ")
    end
  end
end

And that's exactly what we get back. As said in the previous section, if we exclude the slash we'd get the same result because Rack will add it for us.

require "../../lucid_http"

GET "/"
path                            # => "http://localhost:9292/"
body                            # => "Root Path"

GET ""
path                            # => "http://localhost:9292"
body                            # => "Root Path"

Now, this is a route that we'll end up writing for every single app we work on, and the current syntax is not very pretty. Luckily, Roda gives us a convenience method for this particular route called, you guessed it, root.

Lets change this example to use it.

class App < Roda
  route do |r|
    r.root do
      "Root Path"
    end

    # ...
  end
end

If we perform the requests again, the root matcher will execute both of them, the same as before.

require "../../lucid_http"
require "json"

GET "/"
path                            # => "http://localhost:9292/"
body                            # => "Root Path"

What this matcher actually does is match if the remaining path is / and the request method is GET (we'll dive into request methods in a future section).

What I like about using r.root is that it expresses our intention instead of showing a plain string.

Now, say we actually want to render the post list when we browse to the root path. We could definitely copy and paste the code we have on the posts routing block. But we don't like code duplication.

class App < Roda
  route do |r|
    r.root do
      posts = (1..5).map {|i| "Post #{i}"}
      posts.join(" | ")
    end

    r.is "posts" do
      posts = (1..5).map {|i| "Post #{i}"}
      posts.join(" | ")
    end
  end
end

What we can do instead is redirect to that path. We do so by calling the conveniently named r.redirect method.

class App < Roda
  route do |r|
    r.root do
      r.redirect "posts/"
    end

    r.is "posts" do
      posts = (1..5).map {|i| "Post #{i}"}
      posts.join(" | ")
    end
  end
end

Let's try this out. We request the root route from our browser. Immediately, the browser forwards to the post list URL.

The only difference from the client's point of view is that now, instead of returning a 200 Ok status, we're returning a 302, corresponding to a redirect.

require "../../lucid_http"

GET "/"
path                            # => "http://localhost:9292/"
body                            # => ""
status                          # => "302 Found"

And, if we follow the redirect, we see that we're rendering the desired list.

require "../../lucid_http"

GET "/", follow: true
path                            # => "http://localhost:9292/"
body                            # => "Post 1 | Post 2 | Post 3 | Post 4 | Post 5"
status.to_s                     # => "200 OK"

Now say that we want to be able to fetch a post using the posts/:id path. To do this, unfortunately, we have to separate the code that fetches the posts and the code that renders them.

Then we write the code that renders the single post. Notice that we changed the posts matcher from an r.is to an r.on because now it's not a terminal route anymore.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    # ...
    r.on "posts" do
      posts = (1..5).map {|i| "Post #{i}"}

      r.is :id do |id|
        posts[id.to_i - 1]
      end

      posts.join(" | ")
    end
  end
end

This leaves us with a situation similar to the show/detail one of the previous section, in the sense that we don't want the code that renders the posts list to be buried under sub-routes.

Lets fix this the same way we did on that episode. We move the rendering code before the :id route, then we define an is matcher. At this point in our tree, the matched path is /posts so the path we need to match is the empty string. That's because we already consumed all the path we needed to complete our /posts path.

Finally, we wrap the rendering code into a block.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    # ...
    r.on "posts" do
      posts = (1..5).map {|i| "Post #{i}"}

      # GET "/posts/"
      r.is "" do
        posts.join(" | ")
      end

      r.is :id do |id|
        posts[id.to_i - 1]
      end
    end
  end
end

And everything works as expected.

require "../../lucid_http"

GET "/posts/"
# => "Post 1 | Post 2 | Post 3 | Post 4 | Post 5"

Now, as we saw at the beginning of this lesson, passing an empty string to the is matcher is the same as using r.root. Lets make this change,

r.on "posts" do
  posts = (1..5).map {|i| "Post #{i}"}

  r.root do
    posts.join(" | ")
  end
end

and see if it works here.

require "../../lucid_http"

GET "/posts/"
# => "Post 1 | Post 2 | Post 3 | Post 4 | Post 5"

Yup, that worked! But, why? we're not at the root of our application…

This has to do with the way matchers work in Roda. As we saw on the previous lesson, on and is match against the remaining path. As it turns out, all of the matchers that ship with Roda and match on the path share this behavior.

OK, the matcher works here but, does it make sense to use the root abstraction in a sub-route? That's a matter of taste. I like to use it, I think of it as being at the root path of the current sub-route (or at the root path of what's left). If this doesn't make sense to you, by all means, use an is matcher or any of the ones we're going to learn throughout this course. As stated early on, Roda is not an opinionated library.

Matching using booleans and procs

So far we have this code:

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    # ...
    r.on "posts" do
      posts = (0..5).map {|i| "Post #{i}"}

      r.root do
        posts.join(" | ")
      end

      r.is :id do |id|
        posts[id.to_i]
      end
    end
  end
end

We'll make a small modification. We want the /posts/:id route to render a string showing the post name and the access time.

class App < Roda
  route do |r|
    # ...
    r.on "posts" do
      # ...

      r.is :id do |id|
        post        = posts[id.to_i]
        access_time = Time.now.strftime("%H:%M")

        "Post: #{post} | Accessing at #{access_time}"
      end
    end
  end
end

Now, when we access the post with an id of 2, we get more information.

require "lucid_http"

GET "/posts/2"                  # => "Post: Post 2 | Accessing at 09:53"

But in our attempt to add a feature, we've introduced a sneaky bug. Can you spot it?

What would happen if we looked up a post that doesn't exist? Lets try it out.

require "lucid_http"

GET "/posts/2"                  # => "Post: Post 2 | Accessing at 09:55"
GET "/posts/12"                 # => "Post:  | Accessing at 09:55"

Oops, we're rendering an empty post name, which is far from desirable.

To fix this we can add a conditional to handle the case where the post doesn't exist.

r.is :id do |id|
  post        = posts[id.to_i]
  access_time = Time.now.strftime("%H:%M")

  if post
    "Post: #{post} | Accessing at #{access_time}"
  else
    break
  end
end

And this would work. But if we added another route or set of routes that requires the post to be present, we'd need to add a conditional to every single snippet of rendering code.

Instead, we could use another feature provided by Roda: Boolean matchers.

The concept is pretty straight forward. We pass a boolean value as a parameter to the r.on method and it'll only match for true, taking false as a miss.

Lets try it out. To make things easier, we'll add 2 routes, one matching /true and the other one matching /false. Each one will have a nested r.on route with the corresponding boolean value.

class App < Roda
  route do |r|
    r.on "true" do
      r.on true do
        "I'm in"
      end
    end

    r.on "false" do
      r.on false do
        "I'm in"
      end
    end
  end
end

When we try them out, we see that true matches and false don't.

require "lucid_http"

GET "/true"                     # => "I'm in"
GET "/false"                    # => "STATUS: 404 Not Found"

Now lets use this on our example.

First we fetch the post and right after that, we add an r.on matcher passing as an argument !!post.

r.is :id do |id|
  post = posts[id.to_i]
  r.on !!post do
    access_time = Time.now.strftime("%H:%M")

    "Post: #{post} | Accessing at #{access_time}"
  end
end

In case you haven't come across the !!object notation yet, I'll explain it. The ! is the boolean negation operator. It returns true if an object is falsey and false if it's truthy.

!true                           # => false
!false                          # => true

!"Post 12"                      # => false
!nil                            # => true

But if we apply it twice, it will invert the value twice, always returning a boolean.

!!true                           # => true
!!false                          # => false

!!"Post 12"                      # => true
!!nil                            # => false

The final result is that we forced truthy values to e true and falsey values to be false.

In our example,

r.on !!post do
  access_time = Time.now.strftime("%H:%M")

  "Post: #{post} | Accessing at #{access_time}"
end

if the post was found, we'll execute the block, but if nil was returned, the matcher fails to match.

require "lucid_http"

GET "/posts/2"                  # => "Post: Post 2 | Accessing at 10:09"
GET "/posts/10"                 # => "STATUS: 404 Not Found"

This leaves us with the expected result.

There's one more optimization we could apply to this code. Roda allows us to use Ruby procs as matchers. The way this works is that the matcher will match on truthy values and miss on falsey ones. So, as a first step, we could change !!post into proc {post}

r.is :id do |id|
  post = posts[id.to_i]
  r.on proc { post } do
    access_time = Time.now.strftime("%H:%M")

    "Post: #{post} | Accessing at #{access_time}"
  end
end

And the code still works.

require "lucid_http"

GET "/posts/2"                  # => "Post: Post 2 | Accessing at 10:09"
GET "/posts/10"                 # => "STATUS: 404 Not Found"

Now, you might be thinking "that makes the code more complex and doesn't add much", and you'd be right. We've only added complexity to the mix. But now, we can move the code for finding the post inside the lambda.

r.is :id do |id|
  post = posts[id.to_i]
  r.on proc { posts[id.to_i] } do
    access_time = Time.now.strftime("%H:%M")

    "Post: #{post} | Accessing at #{access_time}"
  end
end

And finally, we can pass the post to the block by adding it to the r.captures array.

r.is :id do |id|
  r.on proc { r.captures[0] = posts[id.to_i] } do |post|
    access_time = Time.now.strftime("%H:%M")

    "Post: #{post} | Accessing at #{access_time}"
  end
end

This way, in one line, we've fetched the post and rendered the appropriate response either if it was found or not.

I don't normally use procs this way, because it get's difficult to read. But they are a tool worth having in our tool belt.

On the next section, we'll take the bull by the horns and write a simple matcher by ourselves.

Custom matchers

We know how to use r.on, r.is and r.root. All of these matchers match against the path of a request. But path is hardly the only differentiator of requests. There are other ways HTTP requests might express their intention. For example, what if we wanted to match against the request method?

The request methods or verbs are a set of words that HTTP defines in order to indicate some action to be performed. The request method we've been using so far is GET (which is the default used by browsers). But there are a number of other verbs, like

What if we wanted to handle a POST request? Say, a POST to the /items/new path, to create a new item on a to-do list app?

First we need to add a route for the new item path, using r.on. Now let's write the code we wish to have.

We write a call to r.on_post and we pass a block. Inside the block, we first define a num variable, which will contain the next item number, calculated as the last index plus 1. Then we add the item name and return the list as a string.

class App < Roda
  ITEMS = []

  route do |r|
    r.on "items/new" do
      r.on_post do
        num = ITEMS.count + 1
        ITEMS << "Item #{num}"
        ITEMS.join(" | ")
      end
    end
  end
end

This code won't work because the on_post method doesn't actually exist yet.

Note
Roda does have helper methods to do exactly what we want here. But let's pretend it doesn't, and define this method ourselves.

Where should we define the on_post method?, Well, in a previous lesson, we learned that the r variable is an instance of a App::RodaRequest, so for now we'll just monkey patch it.

We add our matcher method, but we don't know what tools we have at our disposal. AwesomePrint to the rescue, let's print the list of methods we have available.

class App
  class RodaRequest
    def on_post(&block)
      ap (self.methods - Object.methods).sort
    end
  end
end

If we look closely, we can find out some known matchers, like on, but we also see methods aimed to aid us with routing, for example query_string, path, which returns the whole path that was invoked, remaining_path, which returns the remaining path, and request_method, which returns the HTTP verb for the current request.

[
  # ...
  :path,
  :query_string,
  :remaining_path,
  :request_method,
  # ...
]

Let's try this last one out.

class App
  class RodaRequest
    def on_post(&block)
      ap request_method
    end
  end
end

When we browse to http://localhost/items/new, we see that the returned value is an upcased string representing the actual HTTP verb.

"POST"
127.0.0.1 - - [04/Oct/2016:17:21:31 -0300] "POST /items/new HTTP/1.1" 200 23 0.0308

Now that we know how this works, we can ask if the request method is POST. If it matches, we execute the block using the always method.

We won't dive into the always method, all we need to know right now is that it will ensure to return the response that Roda actually requires after yielding to the block.

class App
  class RodaRequest
    def on_post(&block)
      if request_method == "POST"
        always(&block)
      end
    end
  end
end

Now, if we run a request we see it created the desired item.

And if we repeat the call we see the items getting appended to the list.

require "lucid_http"

POST "/items/new"               # => "Item 1"
POST "/items/new"               # => "Item 1 | Item 2"
POST "/items/new"               # => "Item 1 | Item 2 | Item 3"

Now let's try performing a GET request to the /items/new path. We see a 404 error.

require "lucid_http"

GET "/items/new"
body                            # => ""
status                          # => "404 Not Found"

What happened here? The request was routed trough the items matcher, then it got to the new, where it matched successfully again and finally it was stopped by on_post because the request method was GET instead of POST.

class App < Roda
  ITEMS = []
  route do |r|
    r.on "items" do
      r.on "new" do
        r.on_post do
          num = ITEMS.count + 1
          ITEMS << "Item #{num}"
          ITEMS.join(" | ")
        end
      end
    end
  end
end

By defining our own on_post method, we now have a better idea of just how Roda matchers work. We also have some idea of the kinds of information that are available from the RodaRequest object.

Mind you, monkey-patching the RodaRequest class is not the recommended way to add a new matcher method. We'll take a look at extending the request in a future episode.

We can now learn about the baked-in matchers destined to deal with HTTP verbs.

GET and POST

Ok, we rolled our own on_post method to match requests using a POST verb. If you don't remember it, it looks like this:

class Roda
  class RodaRequest
    def on_post(&block)
      if request_method == "POST"
        always(&block)
      end
    end
  end
end

That's nice, but as you might expect, we don't actually need to write our own matcher method for this. Roda ships with a full set of built-in matchers for all the usual HTTP verbs, like GET, POST and PUT.

If we look at RodaRequest's instance methods, we can find a handful with the HTTP verbs names.

require "roda"

Roda::RodaRequest.instance_methods.sort
# => [
#     :delete?,
#     :get,
#     :get?,
#     :is_get?,
#     :post,
#     :post?,
#     :put?,
# ]

Let's explore some of these. We'll start where we left off the example from last lesson.

require "roda"
require "awesome_print"

class App < Roda
  ITEMS = []
  route do |r|
    r.on "items" do
      r.on "new" do
        r.on_post do
          num = ITEMS.count + 1
          ITEMS << "Item #{num}"
          ITEMS.join(" | ")
        end
      end
    end
  end
end

The only change needed for us to start using baked-in methods to handle this POST request is to replace r.on_post with r.post, I intentionally designed like this to avoid explaining it twice (smart move, huh?).

All the methods that deal with HTTP verbs work the same way. They match the request method and execute the passed block if it matched.

class App < Roda
  ITEMS = []
  route do |r|
    r.on "items" do
      r.on "new" do
        r.post do
          num = ITEMS.count + 1
          ITEMS << "Item #{num}"
          ITEMS.join(" | ")
        end
      end
    end
  end
end

Up to here, they work exactly as our custom matcher. But it doesn't end there. Let's make one little refactoring. We can move the post call up to the root of the path.

With this approach, we could wrap all our POST requests into one big block.

class App < Roda
  ITEMS = []
  route do |r|
    r.post do
      r.on "items" do
        r.on "new" do
          num = ITEMS.count + 1
          ITEMS << "Item #{num}"
          ITEMS.join(" | ")
        end
      end
    end
  end
end

This change is partly a matter of preference. You might prefer to sort your routes by method instead of by their base paths. But there are practical reasons for this as well. For instance, we could then easily impose some extra security around every POST action in the application.

For example, say only admins are allowed to create stuff in our system.

We somehow captured the current user.

We can check the current user's permissions right after we enter the routing block executed by the post matcher, assign a 403 status, which corresponds to a "Forbidden" access code, and finally break out of the block with some relevant message.

require "roda"
require "awesome_print"
require "ostruct"

class App < Roda
  ITEMS = []

  current_user = OpenStruct.new(admin?: false)

  route do |r|
    r.post do
      unless current_user.admin?
        response.status = 403
        break "NOPE, you can't be here."
      end

      r.on "items" do
        r.on "new" do
          num = ITEMS.count + 1
          ITEMS << "Item #{num}"
          ITEMS.join(" | ")
        end
      end
    end
  end
end

If we try to access any route via a POST request, we get our 403 Forbidden status and the message we set.

require "lucid_http"

POST "/items/"
body                            # => "NOPE, you can't be here."
status                          # => 403 Forbidden

POST "/items/new"
body                            # => "NOPE, you can't be here."
status                          # => 403 Forbidden

But if the user is an admin,

current_user = OpenStruct.new(admin?: true)

when we try again, we get a 404 on any route we haven't defined and the expected output on the routes that do. And we only had to write this code once.

require "lucid_http"

POST "/items/"
body                            # => ""
status                          # => 404 Not Found

POST "/items/new"
body                            # => "Item 1"
status                          # => 200 OK

That said, routing first by path and then by verb often leads to less code duplication. In real world applications there are very few instances where routing by request method is the way to go. An example of this case would be an application where the vast majority of requests require one method (probably GET), and only very few cases require another.

Coming back to our previous example, there's a third way we can get the same /items/new route and that's by passing the path to match as the first parameter to r.post, the same way we do for r.is and r.on.

require "roda"
require "awesome_print"
require "ostruct"

class App < Roda
  ITEMS = []

  route do |r|
    r.on "items" do
      r.post "new" do
        num = ITEMS.count + 1
        ITEMS << "Item #{num}"
        ITEMS.join(" | ")
      end
    end
  end
end

Passing an argument can eliminate some unneeded nesting in our route's block. One thing that's important to understand is that when the HTTP verbs methods receive an argument, they behave as terminal matchers. In other words, our post matcher will succeed on /items/new but fail if we add anything after that.

require "lucid_http"

POST "/items/new"
body                            # => "Item 1"
status                          # => "200 OK"

POST "/items/new/123"
body                            # => ""
status                          # => "404 Not Found"

On the flip side, if we remove the argument,

require "roda"
require "awesome_print"
require "ostruct"

class App < Roda
  ITEMS = []

  route do |r|
    r.on "items" do
      r.post do
        # ...
      end
    end
  end
end

the matcher will succeed on any POST request, because it will only match against the request method.

require "lucid_http"

POST "/items/new"
body                            # => "Item 1"
status                          # => "200 OK"

POST "/items/new/123"
body                            # => "Item 1 | Item 2"
status                          # => "200 OK"

Those are the basics on how to use the different verbs.

Passing data in

So far, we've learned how to take a client's request and route it into it's destination. But, what if we wanted to pass data along with the request? Roda allows us to receive data from a client in several ways.

The first one we'll explore is one we've already seen: passing information in the url.

Variable data in the URL often serves a dual role: we want to route requests based on the structure of the URL, but we also want to capture parts of the URL path for later use.

For instance, here we are matching on a request to posts, slash some ID, slash some action.

The segments represented by the :action and :id symbols are special. They are placeholders. Roda will capture whatever text is actually found in their place, and pass that data along to the block.

Let's go ahead and set up the block to receive the data that Roda has captured. We'll use ruby's special "star" syntax to collect the arguments into a single hash variable, which we call args.

In the block, we'll just dump out the args to see what we got.

class App < Roda
route do |r|
r.get "posts", :id, :action do|*args|
        ap args
      end
    end
  end

When we load http://localhost:9292/posts/5/show, we see that we get two arguments, each one representing the corresponding symbol, represented as a string.

Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on 0.0.0.0:9292, CTRL+C to stop
[
    [0] "5",
    [1] "show"
]

Now that we understand how Roda captures and supplies block arguments, let's give our parameters names to match their roles, and update the HTTP body.

class App < Roda
  route do |r|
    r.get "posts", :id, :action do |id, action|
      "You accessed the <#{action}> action for post ##{id}"
    end
  end
end

In this example, we have three segments, handled by a single r.get statement. I normally prefer to separate each segment into a its own nested route.

Let me show you what I mean. Let's add a quasi-restful routing API. First we have a :model route that will match a model name and pass it to the block.

Immediately after, we have a line that will select the appropriate model class. Notice that as soon as the information was available, it got used. That allows us to use this model in any of the nested routes, and that's what we usually want.

In this case, let's say the model is Account, we'll want to either look at all accounts or at a particular one. In either case, we'll need the model, so why wait to fetch it?

class App < Roda
  route do |r|
    r.on :model do |model_name|
      model_class = Object.const_get(model_name.capitalize)

      # ...
    end
  end
end

Then we have the index route, that will match exactly /account/index. and it'll return the accounts list.

class App < Roda
  route do |r|
    r.on :model do |model_name|
      model_class = Object.const_get(model_name.capitalize)

      r.is "index" do
        model_class.all.join(" | ")
      end
    end
  end
end

If we got to here, but index didn't match, we skip that block and try to match the id. If we match the id, that means that we're interested in doing something with a particular instance of the previously selected model, so the next logical step would be to fetch it.

class App < Roda
  route do |r|
    r.on :model do |model_name|
      model_class = Object.const_get(model_name.capitalize)

      r.is "index" do
        model_class.all.join(" | ")
      end

      r.on :id do |id|
        model = model_class.find(id.to_i)

        # ...
      end
    end
  end
end

Finally, once we have the appropriate object on our hands, we can either display the show page, or update it if the request was a POST. We end up with the following code:

class App < Roda
  route do |r|
    r.on :model do |model_name|
      model_class = Object.const_get(model_name.capitalize)

      r.is "index" do
        model_class.all.join(" | ")
      end

      r.on :id do |id|
        model = model_class.find(id.to_i)

        r.is "show" do
          model.to_s
        end

        r.post do
          "Updating #{model}"
        end
      end
    end
  end
end

This is a contrived example, but it illustrates how I go about designing a routing tree.

Coming back to the simplified example, we'll show another way of extracting the data out of the url.

class App < Roda
  route do |r|
    r.get "posts", :id, :action do |id, action|
      "You accessed the <#{action}> action for post number #{id}"
    end
  end
end

Say we only have numeric ids. If we tried to match /posts/my_post/show, our current approach would match and return an id of my_post, which, ideally, would trigger an exception on the target code. On our current example, we just get back a post with the "number" my_post as the id.

require "lucid_http"

GET "/posts/my_post/show"
# => "You accessed the <show> action for post number my_post"

If we are in a similar situation, we can change our matcher argument from a symbol to a regex and change the captures to regular regex matchers, taking into account the type of data we want to get out.

class App < Roda
  route do |r|
    r.is "posts", /(\d+)/, :action do |id, action|
      "You accessed the <#{action}> action for post number #{id}"
    end
  end
end

We make a request to /posts/5/show and we get what we expect, a captured id and action.

But if we pass a non-numeric id, we get a 404 Not Found status, which is exactly what we wanted.

require "lucid_http"

GET "/posts/5/show"
body                            # => "You accessed the <show> action for post number 5"
status                          # => "200 OK"

GET "/posts/my_post/show"
body                            # => ""
status                          # => "404 Not Found"

GET "/posts/a7/show"
body                            # => ""
status                          # => "404 Not Found"

But this is not the recommended way to do it. We can replace that regex with something more intention revealing. We can use the Integer class name

class App < Roda
  route do |r|
    r.is "posts", Integer, :action do |id, action|
      "You accessed the <#{action}> action for post number #{id}"
    end
  end
end

and achieve the same exact result.

require "lucid_http"

GET "/posts/5/show"
body                            # => "You accessed the <show> action for post number 5"
status                          # => "200 OK"

GET "/posts/my_post/show"
body                            # => ""
status                          # => "404 Not Found"

GET "/posts/a7/show"
body                            # => ""
status                          # => "404 Not Found"

And for the action, we can take a similar approach, using the String class name instead

class App < Roda
  route do |r|
    r.is "posts", Integer, String do |id, action|
      "You accessed the <#{action}> action for post number #{id}"
    end
  end
end

We can also pass information to a request using the query string.

The query string is a string that goes at the end of the url, after a question mark, and it consist of a series of key=value pairs separated by ampersands.

Here's an example: we set the post attribute to have a value of 42 and the action attribute to have a value of action

http://localhost:9292?post=42&action=show

This kind of approach is commonly used to pass data to a GET request, and probably the most common use is to pass search terms to a site-wide search. Lets use that as an example for this.

We have an array of articles and we want to perform a search based on a q parameter passed through the query string.

We add a route to match the /search path and we're ready to perform our search.

Now we can take a look at the query_string instance method on the request just by returning it from the block

ARTICLES = [
            "This is an article",
            "This is another article",
            "This is a post",
            "And this is whatever you want it to be",
           ]

class App < Roda
  route do |r|
    r.on "search" do
      r.query_string
    end
  end
end

When we browse to http://localhost:9292/search?q=article, we see that r.query_string returns the actual string.

require "lucid_http"

GET "/search?q=article"
# => "q=article"

We could parse it by hand and extract each key and value, but Roda actually provides another method to help us with that: the r.params method. Lets see what it contains.

class App < Roda
  route do |r|
    r.on "search" do
      ap r.params
    end
  end
end

When we browse to http://localhost:9292/search, passing q as an argument to the query string, we see the parsed parameters as a hash on the terminal (http://localhost:9292/search?q=article).

Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on 0.0.0.0:9292, CTRL+C to stop
{
           "q" => "article",
}

If we add another attribute to the query string, for example category=video, we see the new hash with both keys and their respective values.

127.0.0.1 - - [07/Oct/2016:18:04:28 -0300] "GET /search?q=article HTTP/1.1" 200 44 0.0028
{
           "q" => "article",
    "category" => "video"
}
127.0.0.1 - - [07/Oct/2016:18:11:49 -0300] "GET /search?q=article&category=video HTTP/1.1" 200 44 0.0016

And now we can actually populate our route. We search through the articles for the ones that include the value of the q attribute on the request. And finally we join them in order to return a string.

class App < Roda
  route do |r|
    r.on "search" do
      ARTICLES.select { |article|
        article.include?(r.params["q"])
      }.join(" | ")
    end
  end
end

And, when we search for the article keyword, we get all the articles that include the article string.

require "lucid_http"

GET "/search?q=article"
body                            # => "This is an article | This is another article"

If you're a Rails developer you might be wondering if we can fetch parameters using symbols. When we pass parameters to it, we see that The answer is no. The params method returns is a plain old unaltered Ruby Hash, and hashes differentiate between symbols and strings.

This is another example of how Roda is non-intrusive. It doesn't change Ruby core classes behaviors to suit it's needs.

The last way to pass data we'll talk about, is by submitting a form.

We'll add a small example for this one. We want to route a POST request to http://localhost:9292/articles and accept a content attribute from a form.

We get a 404 status because this route doesn't exist yet.

require "lucid_http"

POST "/articles", form: {content: Time.now.strftime("%H:%M:%S") }
body                            # => ""
status                          # => "404 Not Found"

So, to add it, we define a post matcher with articles as an argument and we now have our route.

How do we extract the data from the request?, well, we actually do it the same way we did for the search, by taking them as a hash from the r.params method.

So, to add an article to the ARTICLES array, we just append the content key. And we're done.

We can now render a string showing the last article (which is the one we just added) and a count of all the existing ones.

r.post "articles" do
  ARTICLES << r.params["content"]
  "Latest: #{ARTICLES.last} | Count: #{ARTICLES.count}"
end

Now, when we try it, we see our new articles being appended.

require "lucid_http"

POST "/articles", form: {content: Time.now.strftime("%H:%M:%S")}
# => "Latest: 12:13:38 | Count: 5"

sleep 2

POST "/articles", form: {content: Time.now.strftime("%H:%M:%S")}
# => "Latest: 12:13:40 | Count: 6"

There are two other methods worth mentioning while we are on this topic. The r.GET method returns the parameters passed on the query string of the request, while the r.POST method returns the parameters passed in the request body.

Both this methods are provided Rack.

Plugins

Roda has a philosophy of being un-opinionated out of the box. If you followed along up to this point, you already know probably 80% of what we could call "Vanilla Roda". We could write a whole app with what we know at the moment.

But there's more to it. Roda also comes with a very complete and extensive set of plugins that you won't wanna miss. This lesson will be an exploration of the plugin system. We'll learn how to browse, and use plugins.

In the subsequent sections we'll explore some of what I think are the most useful plugins that come with Roda.

To find the list of plugins, we can go to the Roda site at http://roda.jeremyevans.net/ and click on the Documentation link. Here we can find a list of Plugins. Clicking on any of the plugins listings will get us detailed information on that plugin.

Lets try one out. We'll go with the public plugin.

On the top of it's documentation page, we see a title and a link pointing to the actual file's documentation.

On the last section, we see a link to all the classes and modules the plugin modifies and the constants and public class methods it defines.

On the body we see a short description of what the plugin actually does. And an example showing us how it should be used.

If we decide to go and actually use one of this plugins and we find ourselves caught on an edge case, or if we want to know how it is written just for fun, we can find the source code for any of them in Roda's Github page (https://github.com/jeremyevans/roda) under the lib/roda/plugins/<plugin name>.rb file.

Lets try this plugin out, so we can see it in action. We have an app without any routes.

require "roda"
require "awesome_print"

class App < Roda
  route do

  end
end

Of course, right now, any request we make will result in a 404 Not Found status. We try with http://localhost:9292/dave.html because reasons and that's exactly what we get.

require "lucid_http"

GET "/dave.html"
status                          # => "404 Not Found"

Now, lets add the plugin. To add any plugin, we need to invoke the plugin class method inside our app class and pass the plugin name as a symbol.

With just that, the plugin is in place. This is how we add any plugin on the list.

With just that, the plugin is in place. Notice that we didn't have to add any new gems to our project, since this is one of Roda's built-in plugins. We also didn't need to add a require statement. Roda arranges to load the needed code based on the name of the plugin.

With the plugin added to our app, we can immediately start using it.

The public plugin adds a helper method called public to the request. When we invoke it inside the route block, it will add a route that will allow us to serve static files inside a specific directory.

require "roda"
require "awesome_print"

class App < Roda
  plugin :public

  route do |r|
    r.public
  end
end

By default, it will serve files from the directory <app dir>/public. Lets create it and add some files to it

Dir.mkdir("./pulic")

%w{chris dave matt pete}.each_with_index do |doc_name, i|
  doc_num = (i+9).to_s
  File.open("#{doc_name}.html", "w") do |f|
    content = "<h2>My name is #{doc_name.capitalize} <h2>\n<h3>and I'm number #{doc_num}</h3>\n"
    f.write(content)
  end
end

Now that we have something there,

$ ls public/
chris.html  dave.html  matt.html  pete.html

we browse again to http://localhost:9292/dave.html, and we see that now we get a rendered page and a 200 OK status.

require "lucid_http"

GET "/dave.html"
body               # => "<h2>My name is Dave <h2>\n<h3>and I'm number 10</h3>\n"
status             # => "200 OK"

Now, say we don't want to serve files out of the public directory. We want to serve files from a directory called static instead.

To do that, we need to configure the plugin. We pass a named parameter called root , with static as the value.

require "roda"
require "awesome_print"

class App < Roda
  plugin :public, root: "static"

  route do |r|
    r.public
  end
end

Now, when the app is restarted, the configuration for the plugin changed. Instead of looking into the public directory, it will look into static. Of course, when we try this, it'll return a 404 Not Found status because the directory doesn't exist yet.

require "lucid_http"

GET "/dave.html"
body                          # => ""
status                        # => "404 Not Found"

But if we rename the public directory to static,

mv public static

and retry, it now works again.

require "lucid_http"

GET "/dave.html"
body               # => "<h2>My name is Dave <h2>\n<h3>and I'm number 10</h3>\n"
status             # => "200 OK"

GET "/pete.html"
body               # => "<h2>My name is Pete <h2>\n<h3>and I'm number 12</h3>\n"
status             # => "200 OK"

This is the common pattern for customizing plugin configuration: we pass named parameters after specifying the name of the plugin.

We've learned how to browse, use and configure plugins. In the next sections, we'll start exploring the plugins library that come with Roda. We'll go in depth into each one of them.

Sessions

A common way to maintain a per-user session is using browser cookies. But, how do we do this in Roda? The easiest and more secure approach is using the sessions plugin that ships with Roda.

require "roda"

class App < Roda
  plugin :sessions, secret: ENV['COOKIE_SECRET']

  # ...
end

Let's see what this means. We'll make a route in our app called intro, which captures a name placeholder.

Inside the route, we'll save the name variable under a session key. We'll call this key "name" as well. As the sessions plugin stores session data using JSON internally, session keys should be strings and not symbols. Trying to access session values using a non-String key will not work.

Then, we'll output a short confirmation that we've introduced ourselves to the app.

Back in our original route, we'll look up the "name" session key. If we find anything there, we use it in the greeting. Otherwise, we output something generic.

require "roda"

class App < Roda
  plugin :sessions, secret: ENV['COOKIE_SECRET']

  route do |r|
    r.is "hello" do
      "<h1>Hello #{r.session["name"] || 'World'}!</h1>"
    end

    r.is "intro", String do |name|
      r.session["name"] = name
      "<h1>It's nice to meet you, #{name}!</h1>"
    end
  end
end

Now let's navigate to http://localhost:9292/intro/federico.

We see that we've been introduced.

Next, we'll navigate to /hello. Even though we didn't pass in a name this time, we're greeted by the name we provided a moment ago.

From this we can see that our user session has been saved from one request to the next. And if we use a browser inspection tool to examine the cookies set by this site, we can even see that a roda.session cookie has been set.

Now, if you were following along, you might have noticed that when we started the app, it didn't actually work, resulting in an exception because we didn't provide a COOKIE_SECRET environment variable.

$ rackup
Traceback (most recent call last):
        4: from config.ru:3:in `<main>'
        3: from config.ru:4:in `<class:App>'
        2: from /some/path/lib/roda.rb:361:in `plugin'
        1: from /some/path/lib/roda/plugins/sessions.rb:192:in `configure'
/some/path/lib/roda/plugins/sessions.rb:166:in `split_secret': sessions plugin :secret option must be a String (Roda::RodaError)

When using browser cookies for session state, it's important that we keep our app's data private by encrypting the cookies. To enable this, we need to supply a secret key to the sessions plugin.

Rather than hardcoding a secret in the source code, we'll take its value from the COOKIE_SECRET environment variable. We use fetch so that Ruby will let us know if we forget to set the COOKIE_SECRET variable.

require "roda"

class App < Roda
  plugin :sessions, secret: ENV.fetch('COOKIE_SECRET')

  # ...
end

Using an environment variable makes it very easy to configure our application without setting up any configuration files. This is important when using certain hosting services, such as Heroku. On the other hand, when we're developing locally, it's tedious to have to set the environment variable every time we start up the app.

$ COOKIE_SECRET=some_secret_string_that_is_at_least_64_characters_long_or_it_will_not_work rackup

To avoid this extra step when developing the app, we'll use the dotenv gem (if you're not familiar with dotenv, check out RubyTapas, Episode 176).

source "https://rubygems.org"

gem "roda", "~> 2.18.0"
gem "thin", "~> 1.7.0"
gem "awesome_print", "~> 1.7.0"
gem "dotenv", "~> 2.1.1"

Then we bundle and pessimize

bp

In order to let dotenv do its magic, we need to load it at the earliest possible moment. In our case, that's the first line of the config.ru file.

We require the gem, then tell it to load the local .env configuration file or files, if any.

require "dotenv"
Dotenv.load

require "./app"

run App

Lastly, we need to set up a local .env file (located at the application's root directory) with the COOKIE_SECRET key. One way to create a secure key is to use Ruby's SecureRandom support:

  ruby -rsecurerandom -e 'puts SecureRandom.base64(64)'
#+end_src

That will output a random string of characters that you can set as the value of the =COOKIE_SECRET= key in the =.env= file.

#+begin_example
  COOKIE_SECRET=some_secret_string_that_is_at_least_64_characters_long_or_it_will_not_work

Remember, this file is just for local development use. We'll be setting the COOKIE_SECRET environment variable some other way when we deploy this application to production.

Then we bundle and pessimize Now as we're writing code, we can start up our app and test it without seeing any scary warnings.

$ rackup
Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on localhost:9292, CTRL+C to stop

And that's how we store session state in a Roda app.

So far, we've learned the bare basics to create a fully functional web application without any extra help. On our next lesson we'll start learning how to bump up our productivity developing Roda apps with the help of plugins provided by Roda.

Empty root

It's time to start exploring the wealth of plugins that Roda provides out of the box. Let's start with one of the simplest plugins. But one that I can't live without. Not because it's absolutely necessary, but because it takes a burden from my mind.

I don't like using trivial examples, but with this one, I think it's the best way to show how the plugin works

Say we have an application with a route that matches exactly /is, and returns a trivial string.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end
  end
end

Lets try this out. We go to http://localhost:9292/is and everything works as expected. But if we add a slash at the end, we get a 404 status.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

What if we use r.on instead of r.is?

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      "ON"
    end
  end
end

Well, that just works. Even with the trailing slash.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "STATUS: 404 Not Found"

GET "/on"                       # => "ON"
GET "/on/"                      # => "ON"

But if we have to add a lot of nested routes under this one, the rendered string will end up buried at the end of the block, and that's not good in my opinion.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      # r.very_very "nested routes"

      "ON ROOT"
    end
  end
end

The way I solve this issue is to add an r.root matcher on top of the block, and all the sub-routes below that.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      r.root do
        "ON ROOT"
      end

      # r.very_very "nested routes"
    end
  end
end

When we try it now, we get the exact opposite situation than what we had with the r.is matcher.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "STATUS: 404 Not Found"

GET "/on"                       # => "STATUS: 404 Not Found"
GET "/on/"                      # => "ON ROOT"

If we apply some logic to this, we can come up with the conclusion that in the r.is case we're dealing with exact matches, and that includes the final slash. And in the r.on case, we're explicitly adding the requirement that we need a root path.

If we use r.root outside any routes,

route do |r|
  r.root do
    "ROOT"
  end

  # every other route
end

We just get the rendered text with a 200 status.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

GET "/on"                       # => "ERROR: 404 Not Found"
GET "/on/"                      # => "ON ROOT"

GET ""                          # => "ROOT"
GET "/"                         # => "ROOT"

It might seam that this logic doesn't apply to a root block placed outside every other route, but we need to remember that there's no real empty route, because Rack ensures that there will be at least a leading slash.

Although I know the theory, this mismatch, always gets me, in spite of the logic. I usually waste at least a couple of minutes trying to figure out this 404 errors.

Lets solve this once and for all. We do this by adding the emty_root plugin.

require "roda"
require "awesome_print"

class App < Roda
  plugin :empty_root

  route do |r|
    r.root do
      "ROOT"
    end

    r.is "is" do
      "IS"
    end

    r.on "on" do
      r.root do
        "ON ROOT"
      end
    end
  end
end

And that's it, when we try it out, it just works. The only case that doesn't match is matching a string with r.is and providing a slash at the end. And this is expected. r.is will always match the exact provided string.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

GET "/on"                       # => "ON ROOT"
GET "/on/"                      # => "ON ROOT"

GET ""                          # => "ROOT"
GET "/"                         # => "ROOT"

In fact, if we append a slash to the block:

require "roda"
require "awesome_print"

class App < Roda
  #plugin :empty_root
  route do |r|
    # ...

    r.is "is/" do
      "IS"
    end

    # ...
  end
end

then the situation gets reverted.

require "lucid_http"

GET "/is"                     # => "ERROR: 404 Not Found"
GET "/is/"                    # => "IS"

empty_root is probably the simplest plugin of all, but it's the first one I add when starting a new Roda project.

Rendering

It's time to start exploring the wealth of plugins that Roda provides out of the box. Let's start with one of the simplest plugins. But one that I can't live without. Not because it's absolutely necessary, but because it takes a burden from my mind.

I don't like using trivial examples, but with this one, I think it's the best way to show how the plugin works

Say we have an application with a route that matches exactly /is, and returns a trivial string.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end
  end
end

Lets try this out. We go to http://localhost:9292/is and everything works as expected. But if we add a slash at the end, we get a 404 status.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

What if we use r.on instead of r.is?

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      "ON"
    end
  end
end

Well, that just works. Even with the trailing slash.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "STATUS: 404 Not Found"

GET "/on"                       # => "ON"
GET "/on/"                      # => "ON"

But if we have to add a lot of nested routes under this one, the rendered string will end up buried at the end of the block, and that's not good in my opinion.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      # r.very_very "nested routes"

      "ON ROOT"
    end
  end
end

The way I solve this issue is to add an r.root matcher on top of the block, and all the sub-routes below that.

require "roda"
require "awesome_print"

class App < Roda
  route do |r|
    r.is "is" do
      "IS"
    end

    r.on "on" do
      r.root do
        "ON ROOT"
      end

      # r.very_very "nested routes"
    end
  end
end

When we try it now, we get the exact opposite situation than what we had with the r.is matcher.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "STATUS: 404 Not Found"

GET "/on"                       # => "STATUS: 404 Not Found"
GET "/on/"                      # => "ON ROOT"

If we apply some logic to this, we can come up with the conclusion that in the r.is case we're dealing with exact matches, and that includes the final slash. And in the r.on case, we're explicitly adding the requirement that we need a root path.

If we use r.root outside any routes,

route do |r|
  r.root do
    "ROOT"
  end

  # every other route
end

We just get the rendered text with a 200 status.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

GET "/on"                       # => "ERROR: 404 Not Found"
GET "/on/"                      # => "ON ROOT"

GET ""                          # => "ROOT"
GET "/"                         # => "ROOT"

It might seam that this logic doesn't apply to a root block placed outside every other route, but we need to remember that there's no real empty route, because Rack ensures that there will be at least a leading slash.

Although I know the theory, this mismatch, always gets me, in spite of the logic. I usually waste at least a couple of minutes trying to figure out this 404 errors.

Lets solve this once and for all. We do this by adding the emty_root plugin.

require "roda"
require "awesome_print"

class App < Roda
  plugin :empty_root

  route do |r|
    r.root do
      "ROOT"
    end

    r.is "is" do
      "IS"
    end

    r.on "on" do
      r.root do
        "ON ROOT"
      end
    end
  end
end

And that's it, when we try it out, it just works. The only case that doesn't match is matching a string with r.is and providing a slash at the end. And this is expected. r.is will always match the exact provided string.

require "lucid_http"

GET "/is"                       # => "IS"
GET "/is/"                      # => "ERROR: 404 Not Found"

GET "/on"                       # => "ON ROOT"
GET "/on/"                      # => "ON ROOT"

GET ""                          # => "ROOT"
GET "/"                         # => "ROOT"

In fact, if we append a slash to the block:

require "roda"
require "awesome_print"

class App < Roda
  #plugin :empty_root
  route do |r|
    # ...

    r.is "is/" do
      "IS"
    end

    # ...
  end
end

then the situation gets reverted.

require "lucid_http"

GET "/is"                     # => "ERROR: 404 Not Found"
GET "/is/"                    # => "IS"

empty_root is probably the simplest plugin of all, but it's the first one I add when starting a new Roda project.

So far, we've been working with very short and clean strings as our views. But when we dive into the world of web development, we immediately find ourselves in need of bigger and more complex views. At this point, constructing them by hand starts getting increasingly difficult and annoying.

Say we need to write the list of tasks in a to-do list application.

We start by returning just a string with the list of tasks one after the other, separated by a new line.

require "roda"
require "awesome_print"

require "./models"

class App < Roda
  plugin :empty_root

  route do |r|
    r.root do
      Item.all.map(&:title).join("\n")
    end
  end
end

When we print it, we get what we expected.

require "lucid_http"

GET "/"
puts body

# >> Play Battletoads
# >> Learn how to force-push
# >> Find radioactive spider
# >> Rescue April
# >> Add red setting to sonic screwdriver
# >> Fix lightsaber
# >> Shine claws
# >> Saw cape
# >> Buy Blue paint
# >> Repaint TARDIS

But now we want a more formatted output. We want a unordered list with a checkbox telling us whether the task is done or not.

To do this we could create an empty string, append to it each component and return it in order for it to be rendered.

I'd say this is not the best code I've written. We could extract it into a separate method or even create some helpers to simplify it. But Roda, as many other frameworks, provides a better solution for this kind of situation: templates.

route do |r|
  r.root do
    result = ""
    result << "<ul>"
    Item.all.each do |task|
      result << "<li class=\"#{ task.done? ? :done : :todo }\">"
      result << "  <input type=\"checkbox\"#{ " checked" if task.done? }>"
      result << "    #{ task.title }"
      result << "</li>"
    end
    result << "</ul>"
    result
  end
end

In order to add templates to Roda we'll add the render plugin to our app.

Now we can remove the messy code and replace it with a call to the render method, passing a template name.

class App < Roda
  plugin :empty_root
  plugin :render

  route do |r|
    r.root do
      render "tasks"
    end
  end
end

Then we try it out to see what we get. But we get an error telling us that our template does not exist. But this is good, because now we know where will the plugin look for it.

As the file extension indicates, the templating language that the plugin expects is ERB.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.erb"

So we create the views/tasks.erb file and directory.

And we paste in our ERB template.

We have a title, then, for each task in the list we have a list item. The item class switches on the state of the li element between done and todo.

Inside the list item we see the expected checkbox. It will be checked if the task is done.

And finally, we see the task title.

<html>
  <head>
    <title>To-Do or not To-Do </title>
  </head>
  <body>
    <h1>To-Do or not To-Do </h1>
    <h3>Tasks</h3>
    <ul>
      <% tasks.each do |task| %>
        <li class="<%= task.done? ? :done : :todo %>">
          <input type="checkbox"<%= " checked" if task.done? %>>
          <%= task.title %>
        </li>
      <% end %>
    </ul>
  </body>
</html>

When we try this again, we get an error saying that the tasks variable is not there. This is expected, because we're not defining it anywhere.

require "lucid_http"

GET "/"
error
# => "NameError: undefined local variable or method `tasks' for #<App:0x00000001bd6ba8>"

We can pass that variable to the view by passing the locals attribute to the render method.

The locals attribute is expected to be a hash, with the names of the variables we expect on the view as symbolic keys, and the contents as values.

class App < Roda
  plugin :empty_root
  plugin :render

  route do |r|
    r.root do
      render "tasks", locals: { tasks: Item.all }
    end
  end
end

And when we render, we see our not so beautiful list.

There's a second way we could have passed data to the view and that's using instance variables. To use them, we just instantiate it and the rest is handled for us.

class App < Roda
  plugin :empty_root
  plugin :render

  route do |r|
    r.root do
      @tasks = Item.all
      render "tasks"
    end
  end
end

The second change we need to do then is to change the use of the tasks variables for the @tasks instance variable in the views.

<!-- ... -->
<% @tasks.each do |task| %>
  <!-- ... -->
<% end %>
</ul>
<!-- ... -->

Both are perfectly reasonable approaches, but I normally use local variables because I like how explicit it is.

Now, we'd like some styling for our list.

Lets do it the easiest way possible. We add a style tag inside the head element.

We don't like the bullets, and we want different colors: red for to-do tasks and green for done ones.

<html>
  <head>
    <title>To-Do or not To-Do </title>
    <style>
      ul { list-style: none; }
      .todo { color: red;}
      .done { color: green;}
    </style>
  </head>
  <body>
    <h1>To-Do or not To-Do </h1>
    <!- ... ->
  </body>
</html>

When we reload, we can see our style applied.

Now, I don't normally use ERB for my projects, I prefer haml instead.

If you like haml or any other tilt supported templating engine, here's how we can add it.

First, we need to add the engine to our Gemfile and run bundle.

gem "haml", "~> 4.0.7"

Then we tell the plugin to use haml as the templating engine.

plugin :render, engine: "haml"

When we reload our page, we see a familiar error message. The same as before, the template is not found and this time it's because now the plugin expects a .haml file.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.haml"

We fix that by changing the extension. And now we can convert our erb into haml.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    %h3 Tasks
    %ul
      - tasks.each do |task|
        %li(class="#{task.done? ? :done : :todo}")
          %input{type: "checkbox", checked: (:checked if task.done?) }
          = task.title

And when we reload, we get our site back on it's feet.

Now that we made the list look prettier, we move to our next feature: showing details for a particular task.

So we add the new route, and render the new template, with the required local variables set.

route do |r|
  r.root do
    render "tasks", locals: { tasks: Item.all }
  end

  r.on "tasks/:id" do |id|
    render "task", locals: { task: Item.find(id) }
  end
end

And then we add the new template.

Here we need to show the task title, whether it was done or not, and it's due date.

There's no extra styling going on this page, so we decide to recycle the CSS from the previous template.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    %h2
      = task.title
      - if task.done?
        %span.done [DONE]
      - else
        %span.todo [TODO]

    %h3
      Due Date:
      = task.due.strftime("%v")

We try this out and we see the new template rendered. For done

And undone tasks.

We now have our two different templates, but something doesn't feel right about this code. We are using the same HTML structure, CSS style, and title, and we're duplicating it for every new page we add.

If we wanted to change anything on our basic layout, say the color of the done tasks to a different shade of green, we'd need to change it in every template.

We can solve that by replacing the render method with a call to view.

route do |r|
  r.root do
    view "tasks", locals: {tasks: Item.all}
  end

  r.on "tasks/:id" do |id|
    view "task", locals: {task: Item.find(id)}
  end
end

When we load any of the two pages we currently have, we see that view is not finding a file called layout.haml in the same path as the previous templates.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/layout.haml"

So, what goes in that template?, Well, just what we want to be shared across the site. And a call to yield in the place we want the unique content to go.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    = yield

And remove the boilerplate out of our other templates.

And now everything works again.

Now say that some time has passed and our small app grew into a big project management system. Our views directory is now full of files relative to different models and concerns.

Imagine that we've been working on some obscure feature on the site and we need to come back to add a detail to the tasks list. Was it called todos, tasks, list? In this mess is difficult to find it.

$ ls views
account.haml    dashboards.haml  layout.haml    product.haml   tools.haml    work.haml
accounts.haml   email.haml       page.haml      products.haml  update.haml   works.haml
avatar.haml     emails.haml      pages.haml     task.haml      updates.haml
avatars.haml    image.haml       picture.haml   tasks.haml     user.haml
course.haml     images.haml      pictures.haml  thing.haml     users.haml
courses.haml    item.haml        post.haml      things.haml    video.haml
dashboard.haml  items.haml       posts.haml     tool.haml      videos.haml

We can solve this by using directories to organize our views.

$ ls views
accounts     courses     emails  items  pictures  products  things  updates  videos
avatars      dashboards  images  pages  posts     tasks     tools   users    works
layout.haml

If we navigate to the tasks directory, we can see a tasks.haml file with a quick scan.

In order to render this view, we can't leave the render call on the root route as is.

route do |r|
  r.root do
    render "tasks", locals: {tasks: Item.all}
  end

  # ...
end

If we reload the root route, we find that Roda is not able to find the template anymore.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.haml"

In order to make it work again, we need to add the relative path to the template. This means adding the tasks/ prefix to the string.

route do |r|
  r.root do
    render "tasks/tasks", locals: {tasks: Item.all}
  end

  # ...
end

This tells Roda exactly where the template is located. And now our site is working again.

We've been passing the template names as strings , but note that both the render and view methods also accepts symbols, so this code:

r.root do
  view "tasks", locals: {tasks: Item.all}
end

can also be written like this:

r.root do
  view :tasks, locals: {tasks: Item.all}
end

It's a small change and it's a matter of taste. I prefer to use strings for consistency's sake. Specifically for when we start using namespaces. For example, if we have this code:

route do |r|
  r.root do
    render "tasks/tasks", locals: {tasks: Item.all}
  end

There's no way of changing the tasks/tasks into a symbol.

If you want to dig deeper into this plugin to learn more advanced configuration options, you can access the documentation page.

We now know how to implement views on our application, but what about partials? On the next lesson we'll learn them.

Render

So far, we've been working with very short and clean strings as our views. But when we dive into the world of web development, we immediately find ourselves in need of bigger and more complex views. At this point, constructing them by hand starts getting increasingly difficult and annoying.

Say we need to write the list of tasks in a to-do list application.

We start by returning just a string with the list of tasks one after the other, separated by a new line.

require "roda"
require "awesome_print"

require "./models"

class App < Roda
  plugin :empty_root

  route do |r|
    r.root do
      Item.all.map(&:title).join("\n")
    end
  end
end

When we print it, we get what we expected.

require "lucid_http"

GET "/"
puts body

# >> Play Battletoads
# >> Learn how to force-push
# >> Find radioactive spider
# >> Rescue April
# >> Add red setting to sonic screwdriver
# >> Fix lightsaber
# >> Shine claws
# >> Saw cape
# >> Buy Blue paint
# >> Repaint TARDIS

But now we want a more formatted output. We want a unordered list with a checkbox telling us whether the task is done or not.

To do this we could create an empty string, append to it each component and return it in order for it to be rendered.

I'd say this is not the best code I've written. We could extract it into a separate method or even create some helpers to simplify it. But Roda, as many other frameworks, provides a better solution for this kind of situation: templates.

route do |r|
  r.root do
    result = ""
    result << "<ul>"
    Item.all.each do |task|
      result << "<li class=\"#{ task.done? ? :done : :todo }\">"
      result << "  <input type=\"checkbox\"#{ " checked" if task.done? }>"
      result << "    #{ task.title }"
      result << "</li>"
    end
    result << "</ul>"
    result
  end
end

In order to add templates to Roda we'll add the render plugin to our app.

Now we can remove the messy code and replace it with a call to the render method, passing a template name.

class App < Roda
  plugin :empty_root
  plugin :render

  route do |r|
    r.root do
      render "tasks"
    end
  end
end

Then we try it out to see what we get. But we get an error telling us that our template does not exist. But this is good, because now we know where will the plugin look for it.

As the file extension indicates, the templating language that the plugin expects is ERB.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.erb"

So we create the views/tasks.erb file and directory.

And we paste in our ERB template.

We have a title, then, for each task in the list we have a list item. The item class switches on the state of the li element between done and todo.

Inside the list item we see the expected checkbox. It will be checked if the task is done.

And finally, we see the task title.

<html>
  <head>
    <title>To-Do or not To-Do </title>
  </head>
  <body>
    <h1>To-Do or not To-Do </h1>
    <h3>Tasks</h3>
    <ul>
      <% tasks.each do |task| %>
        <li class="<%= task.done? ? :done : :todo %>">
          <input type="checkbox"<%= " checked" if task.done? %>>
          <%= task.title %>
        </li>
      <% end %>
    </ul>
  </body>
</html>

When we try this again, we get an error saying that the tasks variable is not there. This is expected, because we're not defining it anywhere.

require "lucid_http"

GET "/"
error
# => "NameError: undefined local variable or method `tasks' for #<App:0x00000001bd6ba8>"

We can pass that variable to the view by passing the locals attribute to the render method.

The locals attribute is expected to be a hash, with the names of the variables we expect on the view as keys, and the contents as values.

class App < Roda
  plugin :empty_root
  plugin :render

  route do |r|
    r.root do
      render "tasks", locals: { tasks: Item.all }
    end
  end
end

And when we render, we see our not so beautiful list.

Browse http://localhost:9292/

Now, we'd like some styling for our list.

Lets do it the easiest way possible. We add a style tag inside the head element.

We don't like the black bullets, and we want different colors: red for to-do tasks and green for done ones.

<html>
  <head>
    <title>To-Do or not To-Do </title>
    <style>
      ul { list-style: none; }
      .todo { color: red;}
      .done { color: green;}
    </style>
  </head>
  <body>
    <h1>To-Do or not To-Do </h1>
    <!- ... ->
  </body>
</html>

When we reload, we can see our style applied.

Browse http://localhost:9292/

Now, I don't normally use ERB for my projects, I prefer haml instead.

If you like haml or any other tilt supported templating engine, here's how we can add it.

First, we need to add the engine to our Gemfile and run bundle.

gem "haml", "~> 4.0.7"

Then we tell the plugin to use haml as the templating engine.

plugin :render, engine: "haml"

When we reload our page, we see a familiar error message. The same as before, the template is not found and this time it's because now the plugin expects a .haml file.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.haml"

We fix that by changing the extension. And now we can convert our erb into haml.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    %h3 Tasks
    %ul
      - tasks.each do |task|
        %li(class="#{task.done? ? :done : :todo}")
          %input{type: "checkbox", checked: (:checked if task.done?) } 
          = task.title

And when we reload, we get our site back on it's feet.

Browse http://localhost:9292/

Now that we made the list look prettier, we move to our next feature: showing details for a particular task.

So we add the new route, and render the new template, with the required local variables set.

route do |r|
  r.root do
    render "tasks", locals: { tasks: Item.all }
  end

  r.on "tasks/:id" do |id|
    render "task", locals: { task: Item.find(id) }
  end
end

And then we add the new template.

Here we need to show the task title, whether it was done or not, and it's due date.

There's no extra styling going on this page, so we decide to recycle the CSS from the previous template.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    %h2
      = task.title 
      - if task.done?
        %span.done [DONE]
      - else
        %span.todo [TODO]

    %h3
      Due Date:
      = task.due.strftime("%v")

We try this out and we see the new template rendered. For done

Browse http://localhost:9292/tasks/3

And undone tasks.

Browse http://localhost:9292/tasks/8

We now have our two different templates, but something doesn't feel right about this code. We are using the same HTML structure, CSS style, and title, and we're duplicating it for every new page we add.

If we wanted to change anything on our basic layout, say the color of the done tasks to a different shade of green, we'd need to change it in every template.

We can solve that by replacing the render method with a call to view.

route do |r|
  r.root do
    view "tasks", locals: {tasks: Item.all}
  end

  r.on "tasks/:id" do |id|
    view "task", locals: {task: Item.find(id)}
  end
end

When we load any of the two pages we currently have, we see that view is not finding a file called layout.haml in the same path as the previous templates.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/layout.haml"

So, what goes in that template?, Well, just what we want to be shared across the site.

We add a call to yield in the place we want the unique content to go.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    :css
      ul { list-style: none; }
      .done { color: green;}
      .todo { color: red;}
  %body
    %h1 To-Do or not To-Do
    = yield

And remove the boilerplate out of our other templates.

And now everything works again.

Now say that some time has passed and our small app grew into a big project management system. Our views directory is now full of files relative to different models and concerns.

Imagine that we've been working on some obscure feature on the site and we need to come back to add a detail to the tasks list. Was it called todos, tasks, list? In this mess is difficult to find it.

$ ls views
account.haml    dashboards.haml  layout.haml    product.haml   tools.haml    work.haml
accounts.haml   email.haml       page.haml      products.haml  update.haml   works.haml
avatar.haml     emails.haml      pages.haml     task.haml      updates.haml
avatars.haml    image.haml       picture.haml   tasks.haml     user.haml
course.haml     images.haml      pictures.haml  thing.haml     users.haml
courses.haml    item.haml        post.haml      things.haml    video.haml
dashboard.haml  items.haml       posts.haml     tool.haml      videos.haml

We can solve this by using directories to organize our views.

$ ls views
accounts  courses     emails  items  pictures  products  things  updates  videos
avatars   dashboards  images  pages  posts     tasks     tools   users    works

If we navigate to the tasks directory, we can see a tasks.haml file with a quick scan.

In order to render this view, we can't leave the render call on the root route as is.

route do |r|
  r.root do
    render "tasks", locals: {tasks: Item.all}
  end

  # ...
end

If we reload the root route, we find that Roda is not able to find the template anymore.

require "lucid_http"

GET "/"
error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/lucid/code/my_app/views/tasks.haml"

In order to make it work again, we need to add the relative path to the template. This means adding the tasks/ prefix to the string.

route do |r|
  r.root do
    render "tasks/tasks", locals: {tasks: Item.all}
  end

  # ...
end

This tells Roda exactly where the template is located. And now our site is working again.

If you want to dig deeper into this plugin to learn more advanced configuration options, you can access the documentation page.

On the next lesson we'll learn how to split up a template into partials.

Partials

We have a To-Do app where we show the tasks list on the root route.

We noticed that when the list grows too long, it starts to get annoying having to scroll up and down to locate the undone tasks.

To solve this we could sort the list by putting the undone tasks first. But we don't want to loose the actual ordering of the list.

What we decide to do is to show a list containing only the undone tasks. In order to do that, we create the /todo route.

And to keep our templates organized, we decide to put all views under a tasks directory, we load the tasks/todo template, and pass the list of the undone items as the tasks local variable to the view.

route do |r|
  r.root do
    view "tasks", locals: { tasks: Item.all }
  end

  r.on "todo" do
    view "tasks/todo", locals: { tasks: Item.todo }
  end
end

Then we create the todo.haml template file.

Since this is just the first pass through this implementation and we already have a stylized version of our tasks, we decide to use the same code for our list items. We can always come back and simplify this code later.

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    %li(class="#{task.done? ? :done : :todo}")
      %input(type="checkbox" checked="#{:checked if task.done?}" )
      = task.title

We try this and it works.

Now that it works we can come back to our code. To simplify the views, we decide to extract the duplicated code into it's own template.

We create a file called task.haml inside the views/tasks directory. And then we copy the li code as is into this new file.

%li(class="#{task.done? ? :done : :todo}")
  %input(type="checkbox" checked="#{:checked if task.done?}" )
  = task.title

Now we need a way to render this file into the templates. Luckily, we already know how to do this, we learned it on the previous lesson.

For the todo view, we first remove the li code. Then we call the render method, with tasks/task as the template name. Finally, we pass the current task as a local variable.

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    = render "tasks/task", locals: { task: task }

We try it and it still works.

This is a good implementation and we could just leave it as it is. But lets go one step further into the organization of our views.

If we look at the views/tasks directory, we see that both files look very similar. But if we look at their respective code, they are two very distinct things.

ls views/tasks
task.haml  todo.haml

todo.haml is a template. And templates are meant to render a whole page on our site. They are what we usually call views in most web frameworks.

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    = render "task", locals: { task: task }

On the other hand, task.haml is meant just to render a portion of the page. In web development this kind of files are commonly known as partials.

%li(class="#{task.done? ? :done : :todo}")
  %input(type="checkbox" checked="#{:checked if task.done?}" )
  = task.title

Ruby on Rails provides a nice convention to make this distinction at the file name level. If the file is meant to be a partial, we prepend it's name with an underscore.

Now this small change breaks our code. That's because the tasks/task.haml file doesn't exist anymore.

require "lucid_http"

GET "/todo"
status                               # => "500 Internal Server Error"
error                                # => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/fedex/Dropbox/lucid_courses/roda/13-partials/my_app/views/tasks/task.haml"

To fix it, we can update the call to render on the todo.haml view adding the missing underscore.

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    = render "tasks/_task", locals: { task: task }

And effectively, that solves the issue.

require "lucid_http"

GET "/todo"
status                               # => "200 OK"
body                                 # => "<!DOCTYPE html>\n<html>\n  <head>\n <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'> ...  </head>\n  <body>\n <h1>To-Do or not To-Do</h1>\n <h3>Undone Tasks</h3>\n <ul>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Add red setting to sonic screwdriver\n </li>\n <li class='todo'>\n ...  </body>\n</html>\n"

So, OK, now our directory listings clearly delineate between full-fledged views and partials. But does that mean we have to also uglify our template code with a bunch of leading underscores?

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    = render "tasks/_task", locals: { task: task }

Instead, let's add the partials plugin to our application.

plugin :partials

Now we can use the partial method instead, and get rid of the underscore.

%h3 Undone Tasks
%ul
  - tasks.each do |task|
    = partial "tasks/task", locals: { task: task }

And our code still works as before.

require "lucid_http"

GET "/todo"
status                               # => "200 OK"
body                                 # => "<!DOCTYPE html>\n<html>\n  <head>\n <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>\n <title>To-Do or not To-Do</title>\n <style>\n ul { list-style: none; }\n .done { color: green;}\n .todo { color: red;}\n </style>\n  </head>\n  <body>\n <h1>To-Do or not To-Do</h1>\n <h3>Undone Tasks</h3>\n <ul>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Add red setting to sonic screwdriver\n </li>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Fix lightsaber\n </li>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Shine claws\n </li>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Saw cape\n </li>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Buy Blue paint\n </li>\n <li class='todo'>\n <input checked='' type='checkbox'>\n Repaint TARDIS\n </li>\n </ul>\n  </body>\n</html>\n"

Note that the partial method is just syntactic sugar on top of the render method. It doesn't change any behavior other than adding an underscore to the template name.

I like to use it on my projects because it provides a more intention revealing way to organize and use our views and partials.

In the next section we'll talk about a plugin to clean up some simple routes.

symbol_views

If you remember on one of the early lessons, we said that:

Following the Sinatra style, by default, if we return a string from one of our routes, it gets set as the body of the response and rendered.

Well, let me emphasize that by default statement. We can modify this behavior with plugins. In this section we'll learn how to use one of those plugins.

Say we want to render an about page on our site. We can just set a route to the /about path and render the about template using the view method.

class App < Roda
  plugin :empty_root
  plugin :render, engine: "haml"

  route do |r|
    r.is "about" do
      view :about
    end
  end
end

Note that we're not passing locals to the view. This will be important in a minute.

We then write the template (app_root/views/about.haml)

%h1 About

And we're ready to render it

require "lucid_http"

GET "/about"
# => "<!DOCTYPE html>\n" +
#    "<html>\n" +
#    "  <head></head>\n" +
#    "  <body>\n" +
#    "    <h1>About</h1>\n" +
#    "  </body>\n" +
#    "</html>\n"

That was simple enough, but we can simplify our code by adding the symbol_views plugin.

plugin :symbol_views

And removing the call to view on our route.

r.is "about" do
  :about
end

When we render it now, we get the same result as before.

require "lucid_http"

GET "/about"
# => "<!DOCTYPE html>\n" +
#    "<html>\n" +
#    "  <head></head>\n" +
#    "  <body>\n" +
#    "    <h1>About</h1>\n" +
#    "  </body>\n" +
#    "</html>\n"

Notice that by using this plugin, we can't pass options to the view (such as locals).

It's a simple solution for mostly static pages. Notice that I said mostly and that's because, even though we can't pass options to the view, we are still able to embed some dynamic Ruby in them. For example, we could add the current time like this

%h1 About

%b
  Time:
  = Time.now.strftime("%H:%M")

and it would be properly rendered

require "lucid_http"

GET "/about"
# => "<!DOCTYPE html>\n" +
#    "<html>\n" +
#    "  <head></head>\n" +
#    "  <body>\n" +
#    "    <h1>About</h1>\n" +
#    "    <b>\n" +
#    "      Time:\n" +
#    "      17:48\n" +
#    "    </b>\n" +
#    "  </body>\n" +
#    "</html>\n"

I never use this one, it doesn't bring much to the table in my opinion, but there is one particular use case in which it can become handy. Say we have multiple semi-static routes like the one showed before.

We could use the symbol_Views plugin to make very concise code. We could start by adding them one by one:

r.is "about"      { :about }
r.is "contact_us" { :contact_us }
r.is "license"   { :license }

And generate them with a loop if the list starts getting long enough

%i[about contact_us license help
   some other routes we need to add].each do |route_name|
  r.is(route_name.to_s) { route_name }
end

I want you to let what we did there sink in for a moment. We created a bunch of routes in one go at runtime. It might look like magic, or even very advanced metaprogramming, but it isn't. It's just a regular loop over some symbols. This is the kind of power that Roda gives us.

In fact, note that I put emphasis on the words at runtime. As with every route in Roda, that loop won't get triggered at all if it's not on a branch we acually got to with our routing.

In the next couple of lessons we'll start taking a look at dealing with assets on our applications.

Rendering raw HTML with the h plugin

We are writing a site that tells us about a mystery guest. We don't know what it is, but we should be able to see it by browsing to the /mystery_guest path. So lets

That's not very helpful. What's going on here? If we use LucidHttp to fetch the page, we see that the result is coming back correctly, it's a Pizza!

require "lucid_http"

GET "/mystery_guest"
# => "#<struct Pizza flavor=\"Mozzarella\">"

But why can't we see that on the browser if the response is getting properly returned?

If we take a closer look, everything breaks after the #. The next string is <struct Pizza flavor="Mozzarella">, which a lot like at HTML tag.

What's the solution for this?, well, one solution would be to implement a to_s method in our pizza class and everything would be fine.

But this approach is too naive. Even though for this particular case is the actual solution, it excludes at least two major considerations:

For both cases, the h plugin provides a solution.

After including it, we can pass the string to render to the h method

class App < Roda
  plugin :h

  route do |r|
    r.is 'mystery_guest' do
      h("The Mystery Gest is: #{mystery_guest}")
    end
  end
end

and the response will be properly escaped

require "lucid_http"

GET "/mystery_guest"
# => "The Mystery Gest is: #&lt;struct Pizza flavor=&quot;Mozzarella&quot;&gt;"

I know, it looks pretty ugly but, on the other hand, the result on the browser is much better

Being h an instance method on the app, we can also call it inside our views.

This comes in handy when we already have a view in place and we notice that a part of it needs escaping.

For example, imagine we had the following view in our route

<div>
  The Mystery Gest is:
  <b style="color: green;">
    <%= mystery_guest %>
  </b>
</div>

Which have the same problem as before

And have the same solution, we just need to add a call to the h method

<div>
  The Mystery Gest is:
  <b style="color: green;">
    <%=h mystery_guest %>
  </b>
</div>

And when we reload the browser we have the expected result

I don't usually include this plugin in my projects by default. In fact I don't do it at all unless I actually need escaping in my app. But whenever I encounter a situation like the one I described, I tend to activate it temporarily mostly as a cheap debugging strategy.

Then again, there are some cases when we actually need escaping, and in those cases I don't hesitate for a second.

json

This is a plugin that I really love. Not only because it's very useful, but because it's a perfect example of the power and the simplicity that Roda brings to the table.

Say we have an application that displays a cinema's billboard.

We have our list of movies. Each one includes it's title, next show time for today and a description.

In order to make quick objects from this, we use OpenStructs.

require "roda"
require "awesome_print"
require "ostruct"

class App < Roda
  plugin :empty_root

  route do |r|

    movies = [
      {
        slug: "infinity-war",
        title: "Avengers Infinity War",
        times: ["15:30", "18:40", "21:45"],
        description: "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe."
      },
      {
        slug: "the-usual-suspects",
        title: "The Usual Suspects",
        times: ["11:10", "15:45"],
        description: "A sole survivor tells of the twisty events leading up to a horrific gun battle on a boat, which began when five criminals met at a seemingly random police lineup."
      },
      {
        slug: "the-matrix",
        title: "The Matrix",
        times: ["17:15", "22:10"],
        description: "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."
      },
    ].map {|movie_hash| OpenStruct.new(movie_hash)}

    # ...
  end
end

And for our routes, we have a /movies path for listing our movies. Each one displays the movie title and a url.

We also have a /movies/<slug> path for displaying a specific movie. Here we show the title, times and description for.

class App < Roda
  route do |r|
    r.on "movies" do
      r.root  do
        movies.map { |movie|
          "#{movie.title}: /movies/#{movie.slug}"
        }.join("\n")
      end

      r.on :slug do |slug|
        movie = movies.find { |m| m[:slug] == slug }

        <<~EOF
          #{movie.title}
          Times: [ #{movie.times.join(" ")} ]
          Description: #{movie.description}
        EOF
      end
    end
  end
end

Once our cinema app has some traction, we decide to create a mobile app. So we need a JSON API for it to consume.

For simplicity, lets make some changes to the current app in order to make it return some JSON.

We first convert the index action to a JSON string. In order to do this, we start with an opening square bracket. Then, for each movie, we need to return the JSON string with it's title and url. And join all of them with a comma. Finally we add a closing square bracket.

We also need to set the content type header of the response to let the client know that we're returning JSON

# ...

r.root  do
  response['Content-Type'] = 'application/json'

  "[" +
    movies.map { |movie|
    "{ \"title\": \"#{movie.title}\", \"url\": \"/movies/#{movie.slug}\" }"
  }.join(", ") +
    "]"
end

# ...

And now, when we take a look at the end result, we see our JSON response

require "lucid_http"
require "json"

GET "/movies", json: true
# => [{"title"=>"Avengers Infinity War", "url"=>"/movies/infinity-war"},
#     {"title"=>"The Usual Suspects", "url"=>"/movies/the-usual-suspects"},
#     {"title"=>"The Matrix", "url"=>"/movies/the-matrix"}]

But this is hardly the best way to do this. Instead, we can require json from the standard library and let it do all the heavy lifting.

We replace all the ugly string concatenation and interpolation with an array of hashes and call to_json on it.

r.root  do
  response['Content-Type'] = 'application/json'

  movies.map {|movie| {title: movie.title, url: "/movies/#{movie.slug}"}}.to_json
end

And everything still works as before.

require "lucid_http"
require "json"

GET "/movies", json: true
# => [{"title"=>"Avengers Infinity War", "url"=>"/movies/infinity-war"},
#     {"title"=>"The Usual Suspects", "url"=>"/movies/the-usual-suspects"},
#     {"title"=>"The Matrix", "url"=>"/movies/the-matrix"}]

Now we move on to the routes for each specific movie.

Here we change the previous string to a simple hash instead of an array.

The title and description are pretty straight forward changes. For the times attribute, we can actually simplify it a lot, since we now need only an array.

And, since we need to set the content type, instead of duplicating the code, we move it to the wrapping block.

# ...

r.on "movies" do
  response['Content-Type'] = 'application/json'

  r.root  do
    movies.map {|movie| {title: movie.title, url: "/movies/#{movie.slug}"}}.to_json
  end

  r.on :slug do |slug|
    movie = movies.find { |m| m[:slug] == slug }

    {
      title:       movie.title,
      times:       movie.times,
      description: movie.description
    }
  end

end

# ...

Lets try this out. It blows up! Why? When we look at the error, we see that we're returning a hash, which means that we forgot to call to_json on our hash.

require "lucid_http"
require "json"

GET "/movies/infinity-war", json: true
# => "STATUS: 500 Internal Server Error"
error

# ~> JSON::ParserError
# ~> 765: unexpected token at 'Roda::RodaError: unsupported block result: {:title=>"Avengers Infinity War", :times=>["15:30", "18:40", "21:45"], :description=>"The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe."}
# ~> \t/home/fedex/.rvm/gems/ruby-2.5.1/gems/roda-3.16.0/lib/roda.rb:803:in `block_result_body'
# ~> \t/home/fedex/.rvm/gems/ruby-2.5.1/gems/roda-3.16.0/lib/roda.rb:393:in `block_result'
# ~> ...

Once we do it,

# ...

r.on :slug do |slug|
  movie = movies.find { |m| m[:slug] == slug }

  {
    title:       movie.title,
    times:       movie.times,
    description: movie.description
  }.to_json
end

# ...

everything starts working.

require "lucid_http"
require "json"

GET "/movies/infinity-war", json: true
# => {"title"=>"Avengers Infinity War",
#     "times"=>["15:30", "18:40", "21:45"],
#     "description"=>
#      "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe."}

This approach has 2 inconveniences. First, we need to set the proper header in order to be good citizens. And second, we run the risk of forgetting to add .to_json to the end of every result (and we're not gonna go back to interpolating strings!).

To mitigate both this inconveniences, Roda ships with the json plugin.

To use it, we just need to add it to our app. And then, every time we return an array or a hash, the plugin will take care of setting the correct headers and returning the correct response.

Also, we don't need to import the json library any more, since it's being imported by the plugin.

class App < Roda
  plugin :empty_root
  plugin :json

  route do |r|

    movies = [
      {
        slug: "infinity-war",
        title: "Avengers Infinity War",
        times: ["15:30", "18:40", "21:45"],
        description: "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe."
      },
      {
        slug: "the-usual-suspects",
        title: "The Usual Suspects",
        times: ["11:10", "15:45"],
        description: "A sole survivor tells of the twisty events leading up to a horrific gun battle on a boat, which began when five criminals met at a seemingly random police lineup."
      },
      {
        slug: "the-matrix",
        title: "The Matrix",
        times: ["17:15", "22:10"],
        description: "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."
      },
    ].map {|movie_hash| OpenStruct.new(movie_hash)}

    r.on "movies" do
      r.root  do
        movies.map {|movie| {title: movie.title, url: "/movies/#{movie.slug}"}}
      end

      r.on :slug do |slug|
        movie = movies.find { |m| m[:slug] == slug }

        {
          title:       movie.title,
          times:       movie.times,
          description: movie.description
        }
      end

    end

  end
end

Lets try it one more time. And everything works.

require "lucid_http"
require "json"

GET "/movies", json: true
# => [{"title"=>"Avengers Infinity War", "url"=>"/movies/infinity-war"},
#     {"title"=>"The Usual Suspects", "url"=>"/movies/the-usual-suspects"},
#     {"title"=>"The Matrix", "url"=>"/movies/the-matrix"}]

GET "/movies/infinity-war", json: true
# => {"title"=>"Avengers Infinity War",
#     "times"=>["15:30", "18:40", "21:45"],
#     "description"=>
#      "The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe."}

Remember way back on the basic routing lesson that we said that Roda interprets anything other than a string as a miss?, well now we can see that behavior can be overwritten by using plugins such as this one, which is a very big win. It also comes back to what I said at the beginning of this lesson, plugins like this one shows the power and simplicity of Roda!

Assets

Serving Assets

Up until now, we've been putting our styles into the layout header. Even though that works for one off tests, it doesn't scale well. In fact, it doesn't scale at all.

If you developed even the tiniest web app, you know that styles belong in a separate file or set of files. The same goes for JavaScript code. Even more so if you intend to use a front-end framework such as Twitter Bootstrap or Zurb Foundation.

In the web development world, all these kinds of files, including images and other media as well, are called assets and, you guessed it, Roda ships with a plugin with the same name that does exactly what we need. But before tackling the plugin, we'll try and understand how asset manipulation works in order to know what exactly is this plugin providing us.

We'll use the same ToDo app as before.

%li(class="#{task.done? ? :todo : :done}")
  %input(type="check-box" checked="#{:checked if task.done?}" )
  = task.title

Currently, we have this CSS styling inside a style tag in our layout.haml file.

ul { list-style: none; }
.toast-success { color: green;}
.toast-danger { color: red;}

The first thing we want to do is move it out to it's own file. We'll create an assets/css directory inside our app, and a file called style.css under it.

We paste in our style code and format it, since we now have more room to play with.

ul {
  list-style: none;
}
.toast-success {
  color: green;
}
.toast-danger {
  color: red;
}

In order to add the CSS to the page, we add a link tag to the head. We pass a rel attribute with stylesheet as the value, and then an href attribute. The value for it needs to be the URL to the actual file. But what's the path to the file? We can't give the actual path to the file on the file system for a couple of reasons. First, security, and second, if we're accessing the site from outside our development machine, we won't be able to access it.

The solution is to add a route for it. Say we want to mimic the same path we have on the server.

%link(href="/assets/css/style.css" rel="stylesheet")/

Right now, if we load the site, we see the contents rendered on the page, but not the styles.

And that's because we don't have a route in place. If we trigger a request to get the file, we get a 404 status.

require "lucid_http"

GET "/assets/css/style.css"
status                          # => 404 Not Found

We won't do anything fancy here. We add a route that matches exactly the path we described, except that we give ourselves some room to change the file name.

Then, inside the routed block, we first need to set the Content-Type header on our response object. This is mandatory if we want our styles to take effect on the page. Then we just read the requested file. Notice that we are assuming that all our css files will be on the same directory.

r.is "assets/css", :filename do |filename|
  r.response['Content-Type'] = "text/css; charset=UTF-8"
  File.read("./assets/css/#{filename}")
end

The time has come to try this out. We load the root path, and we can see the styles applied.

We could take the same approach for all our assets. But that would be tedious. More importantly, there are some important aspects of serving assets that we haven't addressed at all. For instance:

We could write code to do all this by hand, but there's a better approach. It's time to bring out the big guns.

Let's learn how to replace the functionality we just wrote by using the asset plugin. Then, in the next episode, we'll see how that plugin can help us with more advanced features like compilation and minimization.

Now we can remove our custom route for css assets and add the plugin to our app.

To configure this plugin, we add a css attribute and pass the files we want to serve. If we're serving just one file, we can pass it as a string, but I usually go for an array right from the start , because I tend to add a second or third file shortly after.

We will include this files into the template or templates we want them to be present in later on this lesson.

We can do the same thing with our JavaScript files.

Now, we added just a file name but, where will the plugin look for them? Well, I cheated a little. I happen to know that this plugin expects asset files to be in the assets subdirectory, so they are already in the right place.

class App < Roda
  # ...
  plugin :assets, css: ['style.css'], js: ['main.js']

  route do |r|
    # ...
  end
end

So, for style sheets, we'll look inside the assets/css directory and, for JavaScript files, inside assets/js. As we can see, I already created the main.js file.

Is that all? Well, no, if we try to get the style sheet, we get a 404 status.

require "lucid_http"

GET "/assets/css/style.css"
status                          # => 404 Not Found

That's because we don't have a route for that anymore.

The assets plugin adds a new method to our Request called assets.

route do |r|
  r.assets

  # ...
end

Sending this message sets up the needed routes to make the asset paths available. Once this is in place, everything works as before.

But there's one last thing we want to change. Even though our styles work right now, if we wanted to add more css files, they would be served by the server, but not added to the layout, we'd have to add them by hand.

But we can tell the assets plugin to add them for us. We do it by adding a call to the assets method on the view, and pass the :css and :js keywords respectively.

!!!
%html
  %head
    %meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
    %title To-Do or not To-Do
    = assets(:css)
    = assets(:js)
  %body
    %h1 To-Do or not To-Do
    = yield

If we now reload the page and look at the generated html code, we see that the first call to assets will add the same link tag we previously added by hand, and the second call, will create a script tag in order to add the main.js file.

What this shows is that the assets plugin is smart enough to know about JavaScript and CSS files. It understands how to generate the right code to link them into our HTML views.

GET "/"

# >> <!DOCTYPE html>
# >> <html>
# >>   <head>
# >>     <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
# >>     <title>To-Do or not To-Do</title>
# >>     <link rel="stylesheet"  href="/assets/css/style.css" />
# >>     <script type="text/javascript"  src="/assets/js/main.js"></script>
# >>   </head>
# >>   <body>

And it will add one for each css or js file passed to the respective attributes to the plugin.

We learned how to serve assets, in the next lesson we'll talk about assets compilation and minimization.

Asset compilation

Now that we know how to serve our assets, lets come back to our pre-compiled assets concern. Say we want to add some more styling to the site.

First, we need to add the required gems to the Gemfile. We start with sass in order to being able to compile an scss file and coffee-script in order to add some CoffeeScript files.

Notice that the gem we used is coffee-script and not just coffee. I always mix those two up and spend a lot of time trying to figure out why CoffeScript is not found by Roda.

gem "sass", "~> 3.4.22"
gem "coffee-script", "~> 2.4.1"

Say we have an Sass file called style.scss instead of a plain css one.

$done-main: #297329;
$todo-main: #CB3434;
$todo-main: #A53131;

ul {
  list-style: none;
}

.todo {
  color: darken($done-main, 15%);
  background-color: lighten($done-main, 40%);
  border: 1px solid lighten($done-main, 30%);
  margin: 2px;
  &:hover {
    background-color: lighten($done-main, 45%);
    border: 1px solid lighten($done-main, 35%);

  }
}

.done {
  color: darken($todo-main, 15%);
  background-color: lighten($todo-main, 40%);
  border: 1px solid lighten($todo-main, 30%);
  margin: 2px;
  &:hover {
    background-color: lighten($todo-main, 45%);
    border: 1px solid lighten($todo-main, 35%);

  }
}

We first need to change the name to style.scss in the plugin configuration in order for the plugin to pick it up.

plugin :assets, css: ['style.scss']

Now, when we reload, we see the new style.

And if we see the style sheet source code, we see that it's been compiled into css code.

require "lucid_http"

GET "/assets/css/style.scss"
puts body

# >> ul {
# >>   list-style: none; }
# >>
# >> .todo {
# >>   color: #153b15;
# >>   background-color: #90d890;
# >>   border: 1px solid #6bca6b;
# >>   margin: 2px; }
# >>   .todo:hover {
# >>     background-color: #a3dea3;
# >>     border: 1px solid #7ed17e; }
# >>
# >> .done {
# >>   color: #6a1f1f;
# >>   background-color: #eab8b8;
# >>   border: 1px solid #de9191;
# >>   margin: 2px; }
# >>   .done:hover {
# >>     background-color: #f0cccc;
# >>     border: 1px solid #e4a4a4; }

We can do the same thing to our JavaScript file, which is currently empty. We won't add any major functionality here, we just need to see it in action.

We change the file name to add the coffee extension and we add jQuery to the mix, just to have some extra features available.

plugin :assets, css: ['style.scss'], , js: %w[jquery-3.1.1.min.js main.coffee]

And we start writing our CoffeeScript code. We set a callback for when the DOM is ready.

We want to capture a click event on any task. Then we save it's check-box input, and it's checked state. With that information we first toggle the checked property, and then we toggle the todo and done classes.

We could also hide the check-box, but we'll leave it displayed just to show it working.

$ ->
  # $('.task>input').hide()
  $('.task').click (e)->
    input = $(this).find('input')
    done = input.prop('checked')
    input.prop('checked', !done)
    $(this).toggleClass('done').toggleClass('todo')

When we reload the page, we can alter the task state just by clicking on it.

Of course there's no action performed other than just changing colors, but we just want to demonstrate some CoffeeScript in action here.

If we now look at the rendered code, we see the actual rendered JavaScript code.

GET "/assets/js/main.coffee"

# >> (function() {
# >>   $(function() {
# >>     return $('.task').click(function(e) {
# >>       var done, input;
# >>       input = $(this).find('input');
# >>       done = input.prop('checked');
# >>       input.prop('checked', !done);
# >>       return $(this).toggleClass('done').toggleClass('todo');
# >>     });
# >>   });
# >>
# >> }).call(this)

We could also use other transpilers, such as sass for css files or Opal for JavaScript, but the process of getting those to work is similar to what we've just seen.

Now that we have all our assets code working, we're ready for production. But there's one other recommended feature that the assets plugin provides.

Right now, if we look at the generated html code, we see that for each asset file we have an including tag, which means that each compiled asset is served individually.

require "lucid_http"

GET "/"
puts body

# >> <!DOCTYPE html>
# >> <html>
# >>   <head>
# >>     <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
# >>     <title>To-Do or not To-Do</title>
# >>     <link rel="stylesheet"  href="/assets/css/style.scss" />
# >>     <script type="text/javascript"  src="/assets/js/jquery-3.1.1.min.js"></script>
# >>     <script type="text/javascript"  src="/assets/js/main.coffee"></script>
# >>   </head>
# >>   <body>
# >>     <h1>To-Do or not To-Do</h1>
# ...

And we know what the generated css or js code looks like.

require "lucid_http"

GET "/assets/js/main.coffee"
puts body

# >> (function() {
# >>   $(function() {
# >>     return $('.task').click(function(e) {
# >>       var done, input;
# >>       input = $(this).find('input');
# >>       done = input.prop('checked');
# >>       input.prop('checked', !done);
# >>       return $(this).toggleClass('done').toggleClass('todo');
# >>     });
# >>   });
# >>
# >> }).call(this);

But for production we don't need either a bunch of individually served files or beautifully formatted code.

What we need our app to do is perform the smallest amount of calls to the server and to serve the smallest files possible.

We can achieve both of this requirements with a single method call.

class App < Roda
  # ...
  plugin :assets, css: ['style.scss'], js: %w[jquery-3.1.1.min.js main.coffee]
  compile_assets

  route do |r|
      # ...
  end

  # ...

Once we called the compile_assets command, when we load our site, we see just one including tag for each type of asset.

GET "/"

# >> <!DOCTYPE html>
# >> <html>
# >>   <head>
# >>     <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
# >>     <title>To-Do or not To-Do</title>
# >>     <link rel="stylesheet"  href="/assets/app.fb40bba5a9c531bbd1f8ce355018660fd44340e0.css" />
# >>     <script type="text/javascript"  src="/assets/app.2e443fe6debdfafab24beaee3fb3a54565ce2ca8.js"></script>
# >>   </head>
# >> # ...

And if we take a peek at how they look like, we see that all the code of all the files of the same type has been concatenated into one big file.

But they're not minimized yet. In order to do this extra optimization step, we need to add two more gems to our Gemfile. yuicompressor will minimize our style sheets, while uglifier will do it for our js files.

Once they are installed and working, the assets plugin will automatically notice and start using them, without any extra configuration.

gem "yuicompressor", "~> 1.3.3"
gem "uglifier", "~> 3.0.3"

If we now reload our Style sheets and our js files, we can see that they are properly minimized.

One note: when minimization is enabled, the rerun command will sometimes keep restarting the application. I haven't figured out a way to prevent this, so for testing this you may need to use the rackup command without the reloader.

And anyway, it's not a good idea to compile our assets in development mode. When we are on our development environment, it's usually more convenient to have our files separated and readable in order to being able to spot potential or actual bugs.

But we still want our assets to be compiled and minimized in production.

The best way to solve this issue is to conditionally call the compile_assets command depending on our environment.

How to achieve this will depend on how we're managing our environments, but, for example sake, let's say that we check that the ENVIRONMENT environment variable is set to production.

class App < Roda
  # ...
  plugin :assets, css: ['style.scss'], js: %w[jquery-3.1.1.min.js main.coffee]
  compile_assets if ENV["ENVIRONMENT"] == "production"

  route do |r|
      # ...
  end

  # ...

Next, we'll move away from assets and into forms.

Forms and CSRF

We have Backpack application with a form that allows us to add items to an imaginary backpack.

Today we're preparing our backpack for a plane trip to Gotham.

We pack the plane tickets, a book for reading, our cellphone, and our wallet.

While we are doing this, someone manages to sneak in a knife in our backpack using just a simple ruby script.

require "http"

response = HTTP.post "http://localhost:9292/add",
  form: {
         item: "KNIFE"
        }

response
# => #<HTTP::Response/1.1 302 Found {"Location"=>"/form", "Content-Type"=>"text/html", "Content-Length"=>"0", "Connection"=>"close", "Server"=>"thin"}>

Then we add our headphones. But when the page reloads, we find out that out an item that we never put into our Backpack, that pesky knife that will trigger all the alarms on the airport.

This kind of sneaky request represents a great safety threat for our app. This security issue is called Cross Site Request Forgery or CSRF for short.

To solve this problem, we use the csrf plugin. As always, we add it with:

plugin :csrf

This plugin relies on a very simple mechanism. It identifies each client session with a unique token, that we can see by using the csrf_token method.

Lets place it on the form view in order to see it's value.

<pre> <%= csrf_token %> </pre>

When we load the page, we see that it's just a long string. And, if we load it in a private window (which will hold a different session), we see that the value is actually different.

But, how does this prevents an intruder to add a dagger to our backpack? Well, once we added the plugin, Roda will check this token's presence for every POST request.

If Mr. Intruder decides to launch his script again, the response this time will be a 403 Forbidden error. This error code means that they can't access this part of the app.

require "http"

response = HTTP.post "http://localhost:9292/add",
  form: {
         item: "DAGGER"
        }

response
# => #<HTTP::Response/1.1 403 Forbidden {"Content-Type"=>"text/html", "Content-Length"=>"0", "Set-Cookie"=>"rack.session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTI0YWQ1MTliNzE0ZGNhNmI4MjYw%0AYzQ5NzcyZGUwYjhjZjhiYzQxZTc2OWQ5YTVmNTAxYWJkNDhlMWIwZDNjYmEG%0AOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFtYlQrYXpvQ2E0UXZDMUFuQWdiKzdG%0ANnhqMUhIZnV5Z2lmNUg4RWJxbDJRPQY7AEY%3D%0A--64092264f8f822aa59206361266e15f9d5811164; path=/; HttpOnly", "Connection"=>"close", "Server"=>"thin"}>

If we set the raise attribute to true, we can actually trigger a 500 error whenever someone tries to perform a csrf violation.

plugin :csrf, raise: true

And now, if we trigger the request again, we see that we get a Rack::Csrf::InvalidCsrfToken error, meaning that the csrf token didn't match the expected value.

require "http"

response = HTTP.post "http://localhost:9292/add",
  form: {
         item: "DAGGER"
        }

response.body.to_s
# => "Rack::Csrf::InvalidCsrfToken: Rack::Csrf::InvalidCsrfToken\n" +
#    "\t/home/lucid/.rvm/gems/ruby-2.3.1/gems/rack_csrf-2.5.0/lib/rack/csrf.rb:41:in `call'\n" +
#    "\t/home/lucid/.rvm/gems/ruby-2.3.1/gems/rack-2.0.1/lib/rack/session/abstract/id.rb:222:in `context'\n" +
#    ...

Notice that, if we now want to add an item to the backpack, we get the same error in our browser.

That's because we're not sending the csrf token in our request.

In order to send it, we need two pieces of information. First, we need the actual token, which we know how to get using the csrf_token method. and then, the name Roda is expecting for the field. We can obtain this by invoking the csrf_field method.

<form action="/add" method="post">
  <input type="text" value="<%= csrf_token %>" name="<%= csrf_field %>" />
  <div class="input-group">
    <input type="text" name="item" class="form-input"/>
    <input type="submit" value="Add item!" class="btn btn-primary input-group-btn"/>
  </div>
</form>

If we try to add an item now, it actually gets added.

That was a good way to send the token to the server, but it was verbose, and we'll need to add it to every form in our app. Instead of building it by hand, we can use the csrf_tag method,

<form action="/add" method="post">
  <%= csrf_tag %>
  <div class="input-group">
    <input type="text" name="item" class="form-input"/>
    <input type="submit" value="Add item!" class="btn btn-primary input-group-btn"/>
  </div>
</form>

that will do the same thing for us.

Except that the field will be hidden, which is what we'd want anyways. Nobody wants to see an ugly string that means nothing to the application domain, and aside, why would we ever change this field?

If we take a look at the page source, we see the generated hidden field.

require "lucid_http"

GET "/form"
puts body

# >> <html>
# >>   <head>
# >>     ...
# >>   </head>
# >>   <body>
# >>   ...
# >>   <form action="/add" method="post">
# >>     <input type="hidden" name="_csrf" value="pj9t3DQ2MJGrIy2kNWZQLGtQkzyxSxpG32XL8YREnWY=" />
# >>     <div class="input-group">
# >>       <input type="text" name="item" class="form-input"/>
# >>       <input type="submit" value="Add item!" class="btn btn-primary input-group-btn"/>
# >>     </div>
# >>   </form>
# >>
# >> <h1> Backpack contents:</h1>
# >> ...

And now our application is protected against Cross Site Request Forgery!

On our next lesson, we'll take a look at how we can distribute our routes through multiple route blocks.

Routing

Multi route

Say we have been working on a Roda app for some time now. It grew up to a point where it's getting increasingly difficult to locate the place in the code where the next feature or bug fix needs to be written.

Even though we have top-level blocks that may guide us, the reality is that the code nesting, block length, and other things get in the way and slow us down.

We could rely on our editor or IDE to help us with code folding or some other mechanism to make it easier.

But, personally, I prefer to make the code itself more readable than relying on external mechanisms.

But how can we do it? Lets try a small scale experiment in order to figure it out.

We have this minimal app. It has two routes, one for the blog, and one for the store.

class App < Roda
  route do |r|
    r.on "blog" do
      "BLOG"
    end

    r.on "store" do
      "STORE"
    end
  end
end

We can reach each one by using their names, and we get the expected body in both cases.

require "lucid_http"

GET "/blog"                     # => "BLOG"
GET "/store"                    # => "STORE"

The first approach to get some separation would be to separate the app into multiple files. In order to do that, we'd need to write the first block in one file, and then reopen the App class in the second file, add the route, and not forget our contextual route block.

# FIle #1
class App < Roda
  route do |r|
    r.on "blog" do
      "BLOG"
    end
  end
end

# FIle #2
class App < Roda
  route do |r|
    r.on "store" do
      "STORE"
    end
  end
end

When we try this, we find out that the /blog route is not accessible anymore.

require "lucid_http"

GET "/blog"                     # => "STATUS: 404 Not Found"
GET "/store"                    # => "STORE"

This makes sense because we've replaced the route block on the first file with the one on the second file.

This is less than convenient for us.

We could also use some metaprogramming here, but we won't go there. Because there's a much easier solution for this: using the multi_route plugin.

With that plugin loaded, for each of our routes, we can call the r.route method, passing the route name as a parameter. And have each route in a different file.

We could even change the name of the route if we wanted to for some reason.

class App < Roda
  plugin :multi_route
  route do |r|
    r.on "blog" do
      r.route("blog")
    end

    r.on "store" do
      r.route("my_store")
    end
  end
end

class App < Roda
  route("blog") do |r|
    "BLOG"
  end
end

class App < Roda
  route("my_store") do |r|
    "STORE"
  end
end

When we try it, we see that it's working again.

GET "/blog"                     # => "BLOG"
GET "/store"                    # => "STORE"

The multi_route plugin allows us to simplify the code even further. If we name the delegate routes the same as their actual url, we can replace the first block's contents with just a call to r.multi_route.

class App < Roda
  plugin :multi_route
  route do |r|
    r.multi_route
  end
end

class App < Roda
  route("blog") do |r|
    "BLOG"
  end
end

class App < Roda
  route("store") do |r|
    "STORE"
  end
end

And it has the exact same behavior that we started with.

require "lucid_http"

GET "/blog"                     # => "BLOG"
GET "/store"                    # => "STORE"

If we go back to our first example, we can extract every cohesive slice of our application into it's own file. Then require those files. And finally add a call to r.multi_route in our main application file.

# app.rb

require "roda"
require "awesome_print"

require "./blog"
require "./store"

class App < Roda
  plugin :sessions, secret: ENV.fetch('COOKIE_SECRET')

  plugin :multi_route

  route do |r|
    r.multi_route
  end
end
# blog.rb

class App < Roda
  route("blog") do |r|
    "BLOG"
  end
end
# store.rb

class App < Roda
  route("store") do |r|
    "STORE"
  end
end

This leaves us with a much more approachable code base.

I highly recommend using multi_route, specially on large applications, because it increases performance, since it matches all available routing branches in a single matcher, instead of matching each branch separately.

optimized_string_matchers: Adds performance optimized matchers for single string arguments.

slash_path_empty: Considers a path of "/" as an empty path when doing a terminal match.

symbol_matchers: Adds support for symbol-specific matching regexps.

Errors

Building an API

We want to create a json API for the following blogging application

Post = Struct.new(:id, :title)

class App < Roda
  plugin :render, engine: "haml"
  plugin :empty_root

  POSTS = [
           Post.new(1, "My first post"),
           Post.new(2, "Another post"),
           Post.new(3, "A third post"),
          ]

  route do |r|
    r.on "posts" do
      r.root do
        render(:posts, locals: {posts: POSTS})
      end

      r.on :id do |id|
        post = POSTS.find { |p| p.id == id.to_i }
        render(:post, locals: {post: post})
      end
    end
  end
end

If we try out both routes, we get the expected HTML response

require "lucid_http"

GET "/posts"
# => "<ul>\n" +
#    "  <li>My first post</li>\n" +
#    "  <li>Another post</li>\n" +
#    "  <li>A third post</li>\n" +
#    "</ul>\n"

GET "/posts/1"
# => "<h1>My first post</h1>\n" +
#    "<p>This post is titled \"My first post\" and has an id of 1.</p>\n"

When designing both an HTML version and a JSON API in the same Roda application, there are several concerns to figure out regarding to the routing.

First, are we gonna use a .json extension for our API endpoints? I tend to do it because I like to use a JSON extension in my browser that prettifies the JSON output if it has that extension. But it's a matter of personal or team preference.

Are we gonna add a separate top level route to the API or we will reuse the same routes as our HTML application?

Do we want to handle both HTML and JSON responses in the same route or separate them?

And so on and so forth.

In the following sections, we'll create an API trying to cover as many cases as possible using plugins.

But first, let's see what would need to be done to write an API from scratch. For simplicity, lets just create the /posts route and return an appropriate JSON response.

class App < Roda
  # ...
  route do |r|
    r.on "posts" do
      "JSON STRING GOES HERE"
    end
  end
end

Notice that I've removed every single distraction from the routes, we just want to focus on returning json.

Now we can replace the placeholder string with a proper JSON string.

In order to do this, we start by requiring json from the standard library and then calling the to_json method on the posts array.

require "json"
# ...

class App < Roda
  # ...
  route do |r|
    r.on "posts" do
      POSTS.to_json
    end
  end
end

Lets see what we get

require "lucid_http"

GET "/posts", json: true
body
# => ["#<Post:0x000000017a4850>",
#     "#<Post:0x000000017a40a8>",
#     "#<Post:0x000000017a3950>"]
content_type
# => "text/html"

We can see two wrong things in this picture.

First, we'd like to return posts as JSON objects instead of ugly strings.

We can fix this by converting each post to a hash.

POSTS.map(&:to_h).to_json

The second problem is that the response content type should be application/json.

We can fix this one by setting the content_type by hand

response["Content-Type"] = "application/json"

Finally, we end up with the following code

require "json"
# ...

class App < Roda
  # ...

  route do |r|
    r.on "posts" do
      response["Content-Type"] = "application/json"
      POSTS.map(&:to_h).to_json
    end
  end
end

Which produces this output

require "lucid_http"

GET "/posts", json: true
# => [{"id"=>1, "title"=>"My first post"},
#     {"id"=>2, "title"=>"Another post"},
#     {"id"=>3, "title"=>"A third post"}]

Now, we've eliminated completely removed the code to render HTML. But we don't want to loose that ability, so we need a way to detect the mime type the user is actually requesting.

To do this, we ask the HTTP_ACCEPT header on the request if it's asking for JSON. If it is, we render the JSON response, or else, we render HTML

r.on "posts" do
  r.root do
    if request.get_header("HTTP_ACCEPT") =~ /json/
      response["Content-Type"] = "application/json"
      POSTS.map(&:to_h).to_json
    else
      render(:posts, locals: {posts: POSTS})
    end
  end
  # ...
end

That's a fairly small amount of code for what we want, but we'd need to perform it for every JSON endpoint, which can get tedious.

We could create a series of helper methods to, well, help us with it, but instead, lets jump to the next section and do it with plugins.

Conventions

Conventions For Small apps

Roda is not a web framework, it's just a small library that provides tools for building a web application. This means that there isn't a project generator and there isn't any guidance provided by the tools themselves to organize it.

For that reason, there's a Conventions page on the documentation section of the Roda web site.

In this lesson we will explore the conventions for working with small apps.

But, what does the term small app means? Well, there's no mention about this on the site, but I define it as an application with few routes, no more than 8 or 10 maybe. In other words, an app that can be easily understood in a simple scan.

I've created a small To-Do app skeleton following this conventions, lets see how it looks like.

We have a todo.rb file, here is where our Roda application code goes. The convention is to call the file the same as the application. So, if our app is called ToDo, the file name needs to be todo.rb. We'll take a look inside in a moment.

Then we have the Rakefile. It should contain all the app related tasks and the default task should run all the tests.

In the assets directory we put all of our css and JavaScript files . As explained in a previous lesson, the assets plugin uses this directory as default.

The models.rb file should contain all the code related to the database and ORM layer of our app, and on the models directory we should put the ORM models, each in it's separate file.

If we decide to use an ORM that uses migrations to generate and/or update the database, the migrate directory would be the place for them.

Then we have the public directory, where we can put all our static files. This coincides with the default directory that the public plugin uses.

All of our testing code should go either in a spec or test directory, depending on the testing framework we're using.

And finally, we have a views directory to contain all of our views and partials, the same directory that the render plugin defaults to.

.
|-- todo.rb
|-- Rakefile
|-- assets/
|-- models.rb
|-- models/
|-- migrate/
|-- public/
|-- spec/
+-- views/

6 directories, 3 files

Now lets dig into the todo.rb file.

Reading from the top, we first require Roda, and the models.rb file, in order to have our models available.

Then we define the ToDo class, which extends from Roda as we've been doing along this entire course.

Then we define any constants we need to have in our app. I usually keep away from constants in my own applications, but that's just a personal preference.

Next, we add all the rack middleware that we'll use in our application.

For example, we decided to use the Rack::Heartbeat middleware to add a heartbeat url to your app.

Then we add all the plugins we'll use in our application. I usually group them by functionality if there are too many of them. I also add a small comment defining the category. It's not necessary, but I found it to be useful for quickly finding plugins to configure.

We follow the plugins by adding the route block. For small apps, we'll only use a single route block with all the routes nested inside.

Finally, we add all the instance methods to be used in our route block or inside the views.

require 'roda'
require './models'

class ToDo < Roda
  ENVIRONMENT = ENV['ENVIRONMENT']

  use Rack::Heartbeat

  # Rendering
  plugin :render
  plugin :assets

  route do |r|
    r.on "something" do
      # ...
    end

    # ...
  end

  def panel(args={}, &block)
    "<div class='panel'>#{block.call}</div>"
  end
end

Those are the conventions defined on Roda's documentation. I'd also like to add a couple of personal additions that you might find in any of my own Roda applications.

I like to load my Roda app inside a config.ru file.

require "./todo"
run ToDo

That way, we can load the application by running the rackup command without any arguments. rackup takes config.ru as the default file to load an application, but if there isn't one present, we need to pass it as an argument.

rackup
rackup todo.rb

Another addition I usually make is using the dotenv gem in order to being able to load environment variables from a file in development. Dotenv should be loaded as soon as possible in our application in order to have the environment variables set from the very beginning of the loading process.

That's why, I usually load it in the first two lines of the config.ru file. There's no earlier than that.

require "dotenv"
Dotenv.load

require "./to_do"
run ToDo

On the next lesson we'll take a look at the conventions proposed for larger applications.

Conventions For Larger apps

Once our application starts growing, it's useful to progressively move to a structure that facilitates the growth.

In the Roda site, there's a set of conventions for large apps.

We could define a large application as one with many routes and some nesting going on.

If we know that our application will become large, we could create it with this structure in mind. But that's not necessary, we can start small and refactor our way up app as we go.

Let's take a look at the proposed structure.

As on the previous list, we start with our application's main file. And we preserve the convention of using the application name.

We also keep the Rakefile, assets/, models.rb, models/, and public/.

The first difference is that we moved all the view and routing helper methods from the main file to a directory called helpers/. I like to separate my helper methods into different files and gather them according to their functionality.

For larger apps is better to have our routes properly organized in separate files according to their particular concerns. For that purpose, we use the multi_route plugin and move all the routes to the routes/ directory. This is the most noticeable difference between both approaches.

The spec directory now should present separate directories for model code and web related code.

And finally, we also separate the views sub-directory according to the particular view concern. The concerns should match the ones we create for our routes.

./blog
|-- app_name.rb
|-- Rakefile
|-- assets/
|-- helpers/
|-- migrate/
|-- models.rb
|-- models/
|-- public/
|-- routes/
|   --- admin.rb
|   +-- auth.rb
|-- spec/
|   --- models/
|   +-- web/
+-- views/
    --- admin/
    +-- auth/

Apart from this conventions, I also maintain my own set of additions. For starters, I keep the conventions mentioned on the previous lesson.

Just a quick recap:

require "dotenv"
Dotenv.load

require "./to_do"
run ToDo

I also like to load the assets plugin in a separate file. I do this because, when the application becomes large, I tend to use a lot of css and JavaScript files.

Apendix

The Lucid HTTP gem

Recently, I was in the process of writing this book and a course about Roda. From the very beginning I needed a way to show the interactions between the user and the server. I didn't like the idea of relying too heavily in the browser. I really like the idea of showing as much as I can in plain text, without leaving Emacs or printing browser images on the book.

Instead of going out looking for a library or application to help me with this, I decided to just write a quick dirty snippet using the http.rb library I learned in episode 428 of RubyTapas.

Unfortunately, I didn't save the snippet, but it went something like this:

require "http"

res = HTTP.get("http://localhost:9292/hello")
res.body.to_s                   # => "<h1>Hello World!<h1>"
res.status.to_s                 # => "200 OK"

Which was too verbose for what I needed.

The HTTP.rb library is excellent for making HTTP requests. It's powerful and flexible enough for my needs. But it's aimed at performing the interactions, instead of showing them in a digestible way.

So I started adding a couple of methods to help me clean the code and developed a small DSL that evolved into a new library I called lucid_http.

For the purpose of demonstration, I created a small app that will allow me to show the gem at work. The code for this app is included in the book bundle, by the name of micro_app.zip.

In order to get it working, first you need to download it, unzip it and cd into it's directory.

$ code unzip echo_app.zip
Archive:  echo_app.zip
   creating: echo_app/
  inflating: echo_app/Gemfile
  inflating: echo_app/micro_app.rb
  inflating: echo_app/config.ru
  inflating: echo_app/Gemfile.lock
   creating: echo_app/.bundle/
 extracting: echo_app/.bundle/config
$ code ls
echo_app  echo_app.zip
$ code cd echo_app
$ echo_app

Once there, run undle to install it's dependencies

  $ echo_app bundle install
  Fetching gem metadata from https://rubygems.org/...............
  Fetching version metadata from https://rubygems.org/.
  Resolving dependencies...
  Using daemons 1.2.4
  Installing eventmachine 1.2.0.1 with native extensions
  Using rack 2.0.1
  Using bundler 1.14.5
  Installing roda 2.18.0
  Using thin 1.7.0
  Bundle complete! 2 Gemfile dependencies, 6 gems now installed.
  Use `bundle show [gemname]` to see where a bundled gem is installed.
$ echo_app

And finally run the rackup command

$ echo_app rackup
Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
  * Min threads: 0, max threads: 16
  * Environment: development
  * Listening on tcp://localhost:9292
  Use Ctrl-C to stop

And you'll have your app up and running to experiment. Once you finished playing, you can just hit Control+c on the terminal and it will stop running.

Now lets start exploring the lucid_http gem.

Say we want to make a GET request to the hello path.

We call the GET method, and pass the required path as an argument.

require "lucid_http"

GET "/hello"
# => "<h1>Hello World!<h1>"

Now, you might be asking yourself what's the URL we're targeting. We can find out calling the target_url method on the LucidHttp module.

require "lucid_http"

LucidHttp.target_url
# => "http://localhost:9292"

We can change it by assigning the new URL. and from now on, every new interaction will be using the new target URL.

require "lucid_http"

LucidHttp.target_url
# => "http://localhost:9292"

LucidHttp.target_url = "http://lucid_code.org"

LucidHttp.target_url
# => "http://lucid_code.org"

Or, if you don't want to actually have the URL displayed on the code, you can just set the TARGET_URL environment variable.

require "lucid_http"

ENV["TARGET_URL"] = "http://lucid_code.org"

LucidHttp.target_url
# => "http://lucid_code.org"

The other thing to notice is that we're preceding the path with a forward slash. This is intentional. If we remove it, we'd get an error. By design, lucid_http takes the target URL as is. It doesn't modify it at all. By not prepending a slash, we're appending the hello string to the 9292 port, which causes the error.

require "lucid_http"

GET "/hello"
# => "<h1>Hello World!<h1>"

GET "hello" # ~> Addressable::URI::InvalidURIError: Invalid port number: "9292hello"
# =>

# ~> Addressable::URI::InvalidURIError
# ~> Invalid port number: "9292hello"
# ~>
# ~> /home/fedex/.rvm/gems/ruby-2.3.3/gems/addressable-2.5.0/lib/addressable/uri.rb:1373:in `port='
# ~> /home/fedex/.rvm/gems/ruby-2.3.3/gems/addressable-2.5.0/lib/addressable/uri.rb:804:in `block in initialize'
# ~> /home/fedex/.rvm/gems/ruby-2.3.3/gems/addressable-2.5.0/lib/addressable/uri.rb:2355:in `defer_validation'
# ~> /home/fedex/.rvm/gems/ruby-2.3.3/gems/addressable-2.5.0/lib/addressable/u
# ...

By calling the GET method we're returning the body of our response. But, what about other relevant information? Well, lucid_http provides a series of methods aimed at showing more than just the raw body.

We can see the status code, the content type, and the full path we requested.

We also have a body method that will return the rendered body in case we need to manipulate it in any way.

require "lucid_http"

GET "/hello/you"
status                          # => 200 OK
status.to_i                     # => 200
content_type                    # => "text/html"
path                            # => "http://localhost:9292/hello/you"
body[/\>(.+)\</, 1]             # => "Hello, You!"

When we decide to make the next request, the current information gets cleaned up, and the new request starts with a clean slate.

require "lucid_http"

GET "/hello/you"
status                          # => 200 OK
content_type                    # => "text/html"
path                            # => "http://localhost:9292/hello/you"
body[/\>(.+)\</, 1]             # => "Hello, You!"

GET "/403"
status                          # => 403 Forbidden
content_type                    # => "text/html"
path                            # => "http://localhost:9292/403"
body                            # => "The request returned a 403 status."

Let's take a look at a different example. If we make a request to /over_there, we get a 302 status. This means that we've been redirected to another URL. If we take a look at the rendered body, we see that it's empty.

require "lucid_http"

GET "/over_there"
status                          # => 302 Found
body                            # => ""

But if we look at it on a browser, we get a different result.

That's because the GET method doesn't automatically follow redirects. Well, it doesn't unless we tell it to, by passing the follow: true argument.

require "lucid_http"

GET "/over_there", follow: true
status                          # => 200 OK
body                            # => "You have arrived here due to a redirection."

Up to here, we've been dealing with "successful" requests. But what happens when we it doesn't succeed? For example, say we have a route with some kind of error. As expected, we get the correct status code. But when we render the body, we see a long ugly string with the whole backtrace. This is far from ideal for presentation purposes.

require "lucid_http"

GET "/500"
status                          # => 500 Internal Server Error
body                            # => "ArgumentError: wrong number of arguments (given 0, expected 2+)\n\t/home/fedex/Dropbox/lucid_screencasts/screencasts/1-lucid_http/code/my_app/app.rb:30:in `initialize'\n\t/home/fedex/Dropbox/lucid_screencasts/screencasts/1-lucid_http/code/my_app/app.rb:30:in `exception'\n\t/home/fedex/Dropbox/lucid_screencasts/scree ..."

For this cases, lucid_http provides an error method that will only return the first line. This is easier on the eyes.

Quick disclaimer
this has only been tested using the Roda library. Other web libraries and frameworks might show 500 pages differently.
require "lucid_http"

GET "/500"
status                          # => 500 Internal Server Error
error                           # => "SocketError: SocketError"

Now, if the request doesn't return a 500 code, lucid_code will be nice enough to let us know.

require "lucid_http"

GET "/not_500"
status                          # => 200 OK
error                           # => "No 500 error found."

Now say we have an echo URL. It returns whatever we say. If we append the .json extension, we get a JSON response.

require "lucid_http"

GET "/hello_world"
# => "You said: hello_world"

GET "/hello_world.json"
# => "{\"content\":\"You said: hello_world\",\"keyword\":\"hello_world\",\"timestamp\":\"2016-12-31 15:00:42 -0300\",\"method\":\"GET\",\"status\":200}"

Now, that doesn't look easy on the eyes. Lets format it as a hash, by passing the jton: true argument. That's better.

require "lucid_http"

GET "/hello_world"
# => "You said: hello_world"

GET "/hello_world.json", json: true
# => {"content"=>"You said: hello_world",
#     "keyword"=>"hello_world",
#     "timestamp"=>"2016-12-31 15:01:06 -0300",
#     "method"=>"GET",
#     "status"=>200}

lucid_http also support a number of other HTTP verbs we can use.

require "lucid_http"

GET     "/verb"                  # => "<GET>"
POST    "/verb"                  # => "<POST>"
PUT     "/verb"                  # => "<PUT>"
PATCH   "/verb"                  # => "<PATCH>"
DELETE  "/verb"                  # => "<DELETE>"
OPTIONS "/verb"                  # => "<OPTIONS>"

Finally, say we want to send some information to the server via a POST request.

We can do it by filling out the query string. Oops, that looks like JSON.

require "lucid_http"

POST "/receive?item=book"
# => "{\"item\":\"book\"}"

But, what if we want to send it as a form? In that case, we just need to add the form as an argument, passing a Hash as the value.

require "lucid_http"

POST "/receive?item=book", json: true
# => {"item"=>"book"}

POST "/receive", json: true, form: { item: "book", quantity: 1, price: 50.0, title: "The complete guide to doing absolutely nothing at all."  }
# => {"item"=>"book",
#     "quantity"=>"1",
#     "price"=>"50.0",
#     "title"=>"The complete guide to doing absolutely nothing at all."}

That was lucid_http. It's not a full featured HTTP library, but it provides the basic functionality you may need when showing off a web app in text only mode.

If there's any use case I missed, you can request it at mailto:info@lucidcode.org or open a pull request on the Github Repository. Requests and contributions are always welcome.