Description

Guide

There's a book called The Guide to Cuba that explains how
to build web applications by following a minimalistic approach. It
is recommended reading for anyone trying to learn the basics of
Cuba and other related tools.

Installation

$ gem install cuba

Usage

Here's a simple application:

# cat hello_world.rbrequire"cuba"require"cuba/safe"Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"Cuba.plugin Cuba::SafeCuba.define do
on get do
on "hello"do
res.write "Hello world!"end
on root do
res.redirect "/hello"endendend

Status codes

If you don't assign a status code and you don't write to the res
object, the status will be set as 404.

For example:

Cuba.define do
on get do
on "hello"do
res.write "hello world"endendend# Requests:## GET / # 404# GET /hello # 200# GET /hello/world # 200

As you can see, as soon as something was written to the response,
the status code was changed to 200.

If you want to match just "hello", but not "hello/world", you can do
as follows:

Cuba.define do
on get do
on "hello"do
on root do
res.write "hello world"endendendend# Requests:## GET / # 404# GET /hello # 200# GET /hello/world # 404

You can also use a regular expression to match the end of line:

Cuba.define do
on get do
on /hello\/?\z/do
res.write "hello world"endendend# Requests:## GET / # 404# GET /hello # 200# GET /hello/world # 404

This last example is not a common usage pattern. It's here only to
illustrate how Cuba can be adapted for different use cases.

If you need this behavior, you can create a helper:

moduleTerminalMatcherdefterminal(path)
/#{path}\/?\z/endendCuba.plugin TerminalMatcherCuba.define do
on get do
on terminal("hello") do
res.write "hello world"endendend

Security

The most important security consideration is to use https for all
requests. If that's not the case, any attempt to secure the application
could be in vain. The rest of this section assumes https is
enforced.

When building a web application, you need to include a security
layer. Cuba ships with the Cuba::Safe plugin, which applies several
security related headers to prevent attacks like clickjacking and
cross-site scripting, among others. It is not included by default
because there are legitimate uses for plain Cuba (for instance,
when designing an API).

Here's how to include it:

require"cuba/safe"Cuba.plugin Cuba::Safe

You should also always set a session secret to some undisclosed
value. Keep in mind that the content in the session cookie is
not encrypted.

Cuba.use(Rack::Session::Cookie, :secret => "__a_very_long_string__")

In the end, your application should look like this:

require"cuba"require"cuba/safe"Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"Cuba.plugin Cuba::SafeCuba.define do
on csrf.unsafe? do
csrf.reset!
res.status =403
res.write("Not authorized")
halt(res.finish)
end# Now your app is protected against a wide range of attacks.
...
end

The Cuba::Safe plugin is composed of two modules:

Cuba::Safe::SecureHeaders

Cuba::Safe::CSRF

You can include them individually, but while the modularity is good
for development, it's very common to use them in tandem. As that's
the normal use case, including Cuba::Safe is the preferred way.

Cross-Site Request Forgery

The Cuba::Safe::CSRF plugin provides a csrf object with the
following methods:

HTTP Verbs

There are four matchers defined for HTTP Verbs: get, post, put and
delete. But the world doesn't end there, does it? As you have the whole
request available via the req object, you can query it with helper methods
like req.options? or req.head?, or you can even go to a lower level
and inspect the environment via the env object, and check for example if
env["REQUEST_METHOD"] equals the obscure verb PATCH.

What follows is an example of different ways of saying the same thing:

on env["REQUEST_METHOD"] =="GET", "api"do ... end
on req.get?, "api"do ... end
on get, "api"do ... end

Actually, get is syntax sugar for req.get?, which in turn is syntax sugar
for env["REQUEST_METHOD"] == "GET".

Request and Response

You may have noticed we use req and res a lot. Those variables are
instances of Rack::Request and Cuba::Response respectively, and
Cuba::Response is just an optimized version of
Rack::Response.

Those objects are helpers for accessing the request and for building
the response. Most of the time, you will just use res.write.

If you want to use custom Request or Response objects, you can
set the new values as follows:

Cuba.settings[:req] =MyRequestCuba.settings[:res] =MyResponse

Make sure to provide classes compatible with those from Rack.

Captures

You may have noticed that some matchers yield a value to the block. The rules
for determining if a matcher will yield a value are simple:

Regex captures: "posts/(\\d+)-(.*)" will yield two values, corresponding to each capture.

Placeholders: "users/:id" will yield the value in the position of :id.

Symbols: :foobar will yield if a segment is available.

File extensions: extension("css") will yield the basename of the matched file.

Parameters: param("user") will yield the value of the parameter user, if present.

The first case is important because it shows the underlying effect of regex
captures.

In the second case, the substring :id gets replaced by ([^\\/]+) and the
string becomes "users/([^\\/]+)" before performing the match, thus it reverts
to the first form we saw.

In the third case, the symbol â€“â€“no matter what it saysâ€“â€“gets replaced
by "([^\\/]+)", and again we are in presence of case 1.

The fourth case, again, reverts to the basic matcher: it generates the string
"([^\\/]+?)\.#{ext}\\z" before performing the match.

The fifth case is different: it checks if the the parameter supplied is present
in the request (via POST or QUERY_STRING) and it pushes the value as a capture.

Composition

You can mount a Cuba app, along with middlewares, inside another Cuba app:

classAPI < Cuba; endAPI.use SomeMiddlewareAPI.define do
on param("url") do |url|
...
endendCuba.define do
on "api"do
run APIendend

If you need to pass information to one sub-app, you can use the
with method and access it with vars:

Testing

Given that Cuba is essentially Rack, it is very easy to test with
Rack::Test, Webrat or Capybara. Cuba's own tests are written
with a combination of Cutest and Rack::Test,
and if you want to use the same for your tests it is as easy as
requiring cuba/test:

Rendering

Cuba includes a plugin called Cuba::Render that provides a couple of helper
methods for rendering templates. This plugin uses Tilt, which serves as
an interface to a bunch of different Ruby template engines (ERB, Haml, Sass,
CoffeeScript, etc.), so you can use the template engine of your choice.

To set up Cuba::Render, do:

require"cuba"require"cuba/render"require"erb"Cuba.plugin Cuba::Render

This example uses ERB, a template engine that comes with Ruby. If you want to
use another template engine, one supported by Tilt, you need to
install the required gem and change the template_engine setting as shown
below.

Cuba.settings[:render][:template_engine] ="haml"

The plugin provides three helper methods for rendering templates: partial,
view and render.

Cuba.define do
on "about"do# `partial` renders a template called `about.erb` without a layout.
res.write partial("about")
end
on "home"do# Opposed to `partial`, `view` renders the same template# within a layout called `layout.erb`.
res.write view("about")
end
on "contact"do# `render` is a shortcut to `res.write view(...)`
render("contact")
endend

By default, Cuba::Render assumes that all templates are placed in a folder
named views and that they use the proper extension for the chosen template
engine. Also for the view and render methods, it assumes that the layout
template is called layout.

Contributing

A good first step is to meet us on IRC and discuss ideas. If that's
not possible, you can create an issue explaning the proposed change
and a use case. We pay a lot of attention to use cases, because our
goal is to keep the code base simple. In many cases, the result of
a conversation will be the creation of another tool, instead of the
modification of Cuba itself.

If you want to test Cuba, you may want to use a gemset to isolate
the requirements. We recommend the use of tools like dep and
gs, but you can use similar tools like gst or bs.

The required gems for testing and development are listed in the
.gems file. If you are using dep, you can create a gemset
and run dep install.