Slide 1

Slide 1 text

Building web framework with Rack Marcin Kulik EuRuKo, 2010/05/30

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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)

Slide 4

Slide 4 text

Why would you need another framework? "Because world needs yet another framework ;-)" - Tomash

Slide 5

Slide 5 text

No, you probably don't need it actually :)

Slide 6

Slide 6 text

Several mature frameworks Tens of custom/experimental ones "Don't reinvent the wheel", right? But...

Slide 7

Slide 7 text

But it's so easy that you should at least try

Slide 8

Slide 8 text

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 :)

Slide 9

Slide 9 text

What is Rack?

Slide 10

Slide 10 text

Ruby web applications interface library

Slide 11

Slide 11 text

Simplest Rack application run lambda do |env| [200, { "Content-type" => "text/plain" }, ["Hello"]] end

Slide 12

Slide 12 text

Simplest Rack middleware class EurukoMiddleware def initialize(app) @app = app end def call(env) env['euruko'] = 2010 @app.call end end

Slide 13

Slide 13 text

Let's transform it into framework!

Slide 14

Slide 14 text

How does typical web framework look like?

Slide 15

Slide 15 text

Rails Merb Pylons Django Rango

Slide 16

Slide 16 text

Looks like MVC, more or less

Slide 17

Slide 17 text

What features we'd like to have?

Slide 18

Slide 18 text

dependencies management RESTful routing controllers (session, flash messages) views (layouts, templates, partials) ORM authentication testing console

Slide 19

Slide 19 text

Available Rack middleware and tools we can use

Slide 20

Slide 20 text

(1/8) Gem dependency management

Slide 21

Slide 21 text

bundler "A gem to bundle gems" github.com/carlhuda/bundler

Slide 22

Slide 22 text

# Gemfile source "http://gemcutter.org" gem "rack" # config.ru require "bundler" Bundler.setup Bundler.require

Slide 23

Slide 23 text

(2/8) Routing

Slide 24

Slide 24 text

Usher "Pure ruby general purpose router with interfaces for rails, rack, email or choose your own adventure" github.com/joshbuddy/usher

Slide 25

Slide 25 text

# Gemfile gem "usher" # config.ru require APP_ROOT / "config" / "router.rb" run Foobar::Router

Slide 26

Slide 26 text

# 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

Slide 27

Slide 27 text

(3/8) Controller

Slide 28

Slide 28 text

Let's build our base controller every action is valid Rack endpoint value returned from action becomes body of the response

Slide 29

Slide 29 text

# 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

Slide 30

Slide 30 text

# config.ru require APP_ROOT / "lib" / "base_controller.rb" Dir[APP_ROOT / "app" / "controllers" / "*.rb"].each do |f| require f end

Slide 31

Slide 31 text

Now we can create UsersController

Slide 32

Slide 32 text

# app/controllers/users_controller.rb class UsersController < Foobar::BaseController def index "Hello there!" end end

Slide 33

Slide 33 text

Controllers also need following: session access setting flash messages setting HTTP headers redirects url generation

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

# 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

Slide 36

Slide 36 text

# 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

Slide 37

Slide 37 text

Now we can use #session, #flash and #redirect_to

Slide 38

Slide 38 text

# 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

Slide 39

Slide 39 text

(4/8) Views

Slide 40

Slide 40 text

Tilt "Generic interface to multiple Ruby template engines" github.com/rtomayko/tilt

Slide 41

Slide 41 text

# Gemfile gem "tilt"

Slide 42

Slide 42 text

# 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

Slide 43

Slide 43 text

(5/8) ORM

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

# 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

Slide 46

Slide 46 text

(6/8) Authentication

Slide 47

Slide 47 text

Warden "General Rack Authentication Framework" github.com/hassox/warden

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

# 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

Slide 50

Slide 50 text

# 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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

(7/8) Testing

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

# Gemfile gem "rack-test"

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

(8/8) Console

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Installation

Slide 59

Slide 59 text

gem install racksh

Slide 60

Slide 60 text

Example racksh session

Slide 61

Slide 61 text

$ racksh Rack::Shell v0.9.7 started in development environment. >> $rack.get "/" => #"text/html", "Content-Length"=>"1812"} @status=200, ... >> User.count => 123

Slide 62

Slide 62 text

Questions?

Slide 63

Slide 63 text

That's it! Example code available at: github.com/sickill/example-rack- framework email: marcin.kulik at gmail.com / www: ku1ik.com / twitter: @sickill