Skip to content

codereading/cuba

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cuba

n. a microframework for web development.

Cuba and Rum, by Jan Sochor

Community

Meet us on IRC: #cuba.rb on freenode.net

Description

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.

Usage

Here's a simple application:

# cat hello_world.rb
require "cuba"

Cuba.use Rack::Session::Cookie

Cuba.define do
  on get do
    on "hello" do
      res.write "Hello world!"
    end

    on root do
      res.redirect "/hello"
    end
  end
end

# cat hello_world_test.rb
require "cuba/test"

scope do
  test "Homepage" do
    visit "/"
    assert has_content?("Hello world!")
  end
end

To run it, you can create a config.ru file:

# cat config.ru
require "./hello_world"

run Cuba

You can now run rackup and enjoy what you have just created.

Matchers

Here's an example showcasing how different matchers work:

require "cuba"

Cuba.use Rack::Session::Cookie

Cuba.define do

  # only GET requests
  on get do

    # /
    on root do
      res.write "Home"
    end

    # /about
    on "about" do
      res.write "About"
    end

    # /styles/basic.css
    on "styles", extension("css") do |file|
      res.write "Filename: #{file}" #=> "Filename: basic"
    end

    # /post/2011/02/16/hello
    on "post/:y/:m/:d/:slug" do |y, m, d, slug|
      res.write "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
    end

    # /username/foobar
    on "username/:username" do |username|
      user = User.find_by_username(username) # username == "foobar"

      # /username/foobar/posts
      on "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/following
      on "following" do
        res.write user.following.size #=> "1301"
      end
    end

    # /search?q=barbaz
    on "search", param("q") do |query|
      res.write "Searched for #{query}" #=> "Searched for barbaz"
    end
  end

  # only POST requests
  on post do
    on "login"

      # POST /login, user: foo, pass: baz
      on param("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.
      on true do
        res.write "You need to provide user and pass!"
      end
    end
  end
end

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

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:

  1. Regex captures: "posts/(\d+)-(.*)" will yield two values, corresponding to each capture.
  2. Placeholders: "users/:id" will yield the value in the position of :id.
  3. Symbols: :foobar will yield if a segment is available.
  4. File extensions: extension("css") will yield the basename of the matched file.
  5. 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:

class API < Cuba; end

API.use SomeMiddleware

API.define do
  on param("url") do |url|
    ...
  end
end

Cuba.define do
  on "api" do
    run API
  end
end

Testing

Given 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"

scope do
  test "Homepage" do
    visit "/"

    assert has_content?("Hello world!")
  end
end

To read more about testing, check the documentation for Cutest and Capybara.

Settings

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"

class Users < Cuba; end
class Admin < Cuba; end

Admin.settings[:layout] = "admin"

assert_equal "guest", Users.settings[:layout]
assert_equal "admin", Admin.settings[:layout]

Feel free to store whatever you find convenient.

Rendering

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.plugin Cuba::Render

Cuba.define do
  on default do

    # Within the partial, you will have access to the local variable `content`,
    # that will hold the value "hello, world".
    res.write render("home.haml", content: "hello, world")
  end
end

Note that in order to use this plugin you need to have Tilt installed, along with the templating engines you want to use.

Plugins

Cuba provides a way to extend its functionality with plugins.

How to create plugins

Authoring your own plugins is pretty straightforward.

module MyOwnHelper
  def markdown(str)
    BlueCloth.new(str).to_html
  end
end

Cuba.plugin MyOwnHelper

That'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:

module Render
  def self.setup(app)
    app.settings[:template_engine] = "erb"
  end

  def partial(template, locals = {})
    render("#{template}.#{settings[:template_engine]}", locals)
  end
end

Cuba.plugin Render

This sample plugin actually resembles how Cuba::Render works.

Finally, if a module called ClassMethods is present, Cuba will be extended with it.

module GetSetter
  module ClassMethods
    def set(key, value)
      settings[key] = value
    end

    def get(key)
      settings[key]
    end
  end
end

Cuba.plugin GetSetter

Cuba.set(:foo, "bar")

assert_equal "bar", Cuba.get(:foo)
assert_equal "bar", Cuba.settings[:foo]

Installation

$ gem install cuba

About

Rum based microframework for web development.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 100.0%