KEMBAR78
Building web framework with Rack | PDF
Building web framework
       with Rack
          Marcin Kulik


       EuRuKo, 2010/05/30
I am:
Senior developer @ Lunar Logic Polska -
agile Ruby on Rails development services

Working with web for 10 years

Using Ruby, Python, Java and others - in
love with Ruby since 2006
Open Source contributor:
 CodeRack.org - Rack middleware
 repository

 Open File Fast - Netbeans and JEdit
 plugin

 racksh - console for Rack apps

 and many more (check
 github.com/sickill)
Why would you need
   another framework?
"Because world needs yet another framework ;-)" - Tomash
No, you probably don't need
       it actually :)
Several mature frameworks

Tens of custom/experimental ones

"Don't reinvent the wheel", right?

But...
But it's so easy that you
  should at least try
Rack provides everything you'll need, is
extremely simple but extremely
powerful

It will help you to better understand
HTTP

It will make you better developer

Your custom framework will be the
fastest one *

It's fun! A lot of fun :)
What is Rack?
Ruby web applications interface

library
Simplest Rack application
run lambda do |env|
  [200, { "Content-type" => "text/plain" }, ["Hello"]]
end
Simplest Rack middleware
class EurukoMiddleware
  def initialize(app)
    @app = app
  end
  def call(env)
    env['euruko'] = 2010
    @app.call
  end
end
Let's transform it into
      framework!
How does typical web
framework look like?
Rails

Merb

Pylons

Django

Rango
Looks like MVC, more or less
What features we'd like to
         have?
dependencies management
RESTful routing
controllers (session, flash messages)
views (layouts, templates, partials)
ORM
authentication
testing
console
Available Rack middleware
   and tools we can use
(1/8) Gem dependency
     management
bundler
  "A gem to bundle gems"

github.com/carlhuda/bundler
# Gemfile
source "http://gemcutter.org"
gem "rack"
# config.ru

require "bundler"
Bundler.setup
Bundler.require
(2/8) Routing
Usher
"Pure ruby general purpose router with interfaces for rails, rack,
              email or choose your own adventure"

                  github.com/joshbuddy/usher
# Gemfile

gem "usher"
# config.ru
require APP_ROOT / "config" / "router.rb"

run Foobar::Router
# config/router.rb
module Foobar
  Router = Usher::Interface.for(:rack) do
    get('/').to(HomeController.action(:welcome)).name(:root) # root
    add('/login').to(SessionController.action(:login)).name(:login)
    get('/logout').to(SessionController.action(:logout)).name(:logo
    ...
    default ExceptionsController.action(:not_found) # 404
  end
end
(3/8) Controller
Let's build our base
       controller
every action is valid Rack endpoint

value returned from action becomes
body of the response
# lib/base_controller.rb

module Foobar
  class BaseController
    def call(env)
      @request = Rack::Request.new(env)
      @response = Rack::Response.new
      resp_text = self.send(env['x-rack.action-name'])
      @response.write(resp_text)
      @response.finish
    end
    def self.action(name)
      lambda do |env|
        env['x-rack.action-name'] = name
        self.new.call(env)
      end
    end
  end
end
# config.ru
require APP_ROOT / "lib" / "base_controller.rb"
Dir[APP_ROOT / "app" / "controllers" / "*.rb"].each do |f|
  require f
end
Now we can create
 UsersController
# app/controllers/users_controller.rb
class UsersController < Foobar::BaseController
  def index
    "Hello there!"
  end
end
Controllers also need following:
 session access

 setting flash messages

 setting HTTP headers
 redirects

 url generation
rack-contrib
  "Contributed Rack Middleware and Utilities"
         github.com/rack/rack-contrib


            rack-flash
"Simple flash hash implementation for Rack apps"

        nakajima.github.com/rack-flash
# Gemfile
gem "rack-flash"
gem "rack-contrib", :require => 'rack/contrib'
# config.ru

use   Rack::Flash
use   Rack::Session::Cookie
use   Rack::MethodOverride
use   Rack::NestedParams
# lib/base_controller.rb

module Foobar
  class BaseController
    def status=(code); @response.status = code; end
   def headers; @response.header; end
   def session; @request.env['rack.session']; end

   def flash; @request.env['x-rack.flash']; end
   def url(name, opts={}); Router.generate(name, opts); end
    def redirect_to(url)
      self.status = 302
      headers["Location"] = url
      "You're being redirected"
    end
  end
end
Now we can use #session,
 #flash and #redirect_to
# app/controllers/users_controller.rb

class UsersController < Foobar::BaseController
  def openid
    if session["openid.url"]
      flash[:notice] = "Cool!"
      redirect_to "/cool"
    else
      render
    end
  end
end
(4/8) Views
Tilt
"Generic interface to multiple Ruby template engines"

             github.com/rtomayko/tilt
# Gemfile
gem "tilt"
# lib/base_controller.rb

module Foobar
  class BaseController
    def render(template=nil)
      template ||= @request.env['x-rack.action-name']
      views_path = "#{APP_ROOT}/app/views"
      template_path =
        "#{views_path}/#{self.class.to_s.underscore}/" +
          "#{template}.html.erb"
      layout_path =
        "#{views_path}/layouts/application.html.erb"
      Tilt.new(layout_path).render(self) do
        Tilt.new(template_path).render(self)
      end
    end
  end
end
(5/8) ORM
DataMapper
"DataMapper is a Object Relational Mapper written in Ruby. The
 goal is to create an ORM which is fast, thread-safe and feature
                             rich."

                        datamapper.org
# Gemfile

gem "dm-core"
gem "dm-..."
# app/models/user.rb

class User
  include DataMapper::Resource

  property :id, Serial
  property :login, String, :required => true
  property :password, String, :required => true
end
# config.ru
Dir[APP_ROOT / "app" / "models" / "*.rb"].each do |f|
  require f
end
(6/8) Authentication
Warden
"General Rack Authentication Framework"

      github.com/hassox/warden
# Gemfile

gem "warden"
# config.ru
use Warden::Manager do |manager|
  manager.default_strategies :password
  manager.failure_app =
    ExceptionsController.action(:unauthenticated)
end
require "#{APP_ROOT}/lib/warden.rb"
# lib/warden.rb
Warden::Manager.serialize_into_session do |user|
  user.id
end
Warden::Manager.serialize_from_session do |key|
  User.get(key)
end
Warden::Strategies.add(:password) do
  def authenticate!
    u = User.authenticate(
          params["username"],
          params["password"]
        )
    u.nil? ? fail!("Could not log in") : success!(u)
  end
end
# lib/base_controller.rb
module Foobar
  class BaseController
    def authenticate!
      @request.env['warden'].authenticate!
    end
   def logout!(scope=nil)
     @request.env['warden'].logout(scope)
   end
    def current_user
      @request.env['warden'].user
    end
  end
end
Now we can guard our action:
# app/controllers/users_controller.rb
class UsersController < Foobar::BaseController
  def index
    authenticate!
    @users = User.all(:id.not => current_user.id)
    render
  end
end
(7/8) Testing
rack-test
"Rack::Test is a small, simple testing API for Rack apps. It can be
     used on its own or as a reusable starting point for Web
         frameworks and testing libraries to build on."

                  github.com/brynary/rack-test
# Gemfile
gem "rack-test"
require "rack/test"

class UsersControllerTest < Test::Unit::TestCase
  include Rack::Test::Methods
 def app
   Foobar::Router.new
 end

 def test_redirect_from_old_dashboard
   get "/old_dashboard"
   follow_redirect!
    assert_equal "http://example.org/new_dashboard",
                 last_request.url
    assert last_response.ok?
  end
end
(8/8) Console
racksh (aka Rack::Shell)
 "racksh is a console for Rack based ruby web applications. It's
like Rails script/console or Merb's merb -i, but for any app built
                             on Rack"

                   github.com/sickill/racksh
Installation
gem install racksh
Example racksh session
$ racksh
Rack::Shell v0.9.7 started in development environment.
>> $rack.get "/"
=> #<Rack::MockResponse:0xb68fa7bc @body="<html>...",
   @headers={"Content-Type"=>"text/html", "Content-Length"=>"1812"}
   @status=200, ...
>> User.count
=> 123
Questions?
That's it!
Example code available at: github.com/sickill/example-rack-
                        framework

email: marcin.kulik at gmail.com / www: ku1ik.com / twitter:
                            @sickill

Building web framework with Rack