n. a microframework for web development.
Meet us on IRC: #cuba.rb on freenode.net
Cuba is a microframework for web development originally inspired by Rum, a tiny but powerful mapper for Rack applications.
It integrates many templates via Tilt, and testing via Cutest and Capybara.
Here's a simple application:
# cat hello_world.rbrequire"cuba"Cuba.useRack::Session::CookieCuba.definedoongetdoon"hello"dores.write"Hello world!"endonrootdores.redirect"/hello"endendend# cat hello_world_test.rbrequire"cuba/test"scopedotest"Homepage"dovisit"/"asserthas_content?("Hello world!")endendTo run it, you can create a config.ru file:
# cat config.rurequire"./hello_world"runCubaYou can now run rackup and enjoy what you have just created.
Here's an example showcasing how different matchers work:
require"cuba"Cuba.useRack::Session::CookieCuba.definedo# only GET requestsongetdo# /onrootdores.write"Home"end# /abouton"about"dores.write"About"end# /styles/basic.csson"styles",extension("css")do |file|
res.write"Filename: #{file}"#=> "Filename: basic"end# /post/2011/02/16/helloon"post/:y/:m/:d/:slug"do |y,m,d,slug|
res.write"#{y}-#{m}-#{d}#{slug}"#=> "2011-02-16 hello"end# /username/foobaron"username/:username"do |username|
user=User.find_by_username(username)# username == "foobar"# /username/foobar/postson"posts"do# You can access `user` here, because the `on` blocks# are closures.res.write"Total Posts: #{user.posts.size}"#=> "Total Posts: 6"end# /username/foobar/followingon"following"dores.writeuser.following.size#=> "1301"endend# /search?q=barbazon"search",param("q")do |query|
res.write"Searched for #{query}"#=> "Searched for barbaz"endend# only POST requestsonpostdoon"login"# POST /login, user: foo, pass: bazonparam("user"),param("pass")do |user,pass|
res.write"#{user}:#{pass}"#=> "foo:baz"end# If the params `user` and `pass` are not provided, this block will# get executed.ontruedores.write"You need to provide user and pass!"endendendendThere 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:
onenv["REQUEST_METHOD"] == "GET","api"do ... endonreq.get?,"api"do ... endonget,"api"do ... endActually, get is syntax sugar for req.get?, which in turn is syntax sugar
for env["REQUEST_METHOD"] == "GET".
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:
:foobarwill 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.
You can mount a Cuba app, along with middlewares, inside another Cuba app:
classAPI < Cuba;endAPI.useSomeMiddlewareAPI.definedoonparam("url")do |url|
...
endendCuba.definedoon"api"dorunAPIendendGiven that Cuba is essentially Rack, it is very easy to test with Webrat or
Capybara. Cuba's own tests are written with a combination of Cutest
and Capybara, and if you want to use the same for your tests it is
as easy as requiring cuba/test:
require"cuba/test"require"your/app"scopedotest"Homepage"dovisit"/"asserthas_content?("Hello world!")endendTo read more about testing, check the documentation for Cutest and Capybara.
Each Cuba app can store settings in the Cuba.settings hash. The settings are
inherited if you happen to subclass Cuba
Cuba.settings[:layout]="guest"classUsers < Cuba;endclassAdmin < Cuba;endAdmin.settings[:layout]="admin"assert_equal"guest",Users.settings[:layout]assert_equal"admin",Admin.settings[:layout]Feel free to store whatever you find convenient.
Cuba ships with a plugin that provides helpers for rendering templates. It uses Tilt, a gem that interfaces with many template engines.
require"cuba/render"Cuba.pluginCuba::RenderCuba.definedoondefaultdo# Within the partial, you will have access to the local variable `content`,# that will hold the value "hello, world".res.writerender("home.haml",content: "hello, world")endendNote that in order to use this plugin you need to have Tilt installed, along with the templating engines you want to use.
Cuba provides a way to extend its functionality with plugins.
Authoring your own plugins is pretty straightforward.
moduleMyOwnHelperdefmarkdown(str)BlueCloth.new(str).to_htmlendendCuba.pluginMyOwnHelperThat's the simplest kind of plugin you'll write. In fact, that's exactly how
the markdown helper is written in Cuba::TextHelpers.
A more complicated plugin can make use of Cuba.settings to provide default
values. In the following example, note that if the module has a setup method it will
be called as soon as it is included:
moduleRenderdefself.setup(app)app.settings[:template_engine]="erb"enddefpartial(template,locals={})render("#{template}.#{settings[:template_engine]}",locals)endendCuba.pluginRenderThis sample plugin actually resembles how Cuba::Render works.
Finally, if a module called ClassMethods is present, Cuba will be extended
with it.
moduleGetSettermoduleClassMethodsdefset(key,value)settings[key]=valueenddefget(key)settings[key]endendendCuba.pluginGetSetterCuba.set(:foo,"bar")assert_equal"bar",Cuba.get(:foo)assert_equal"bar",Cuba.settings[:foo]$ geminstallcuba