Skip to content
Browse files

first commit, after SFRuby talk

  • Loading branch information...
0 parents commit 678db79beea834839a12b5deb645a390cf2404e8 @alexch committed
Showing with 501 additions and 0 deletions.
  1. +43 −0 app.rb
  2. +286 −0 off-the-rails.md
  3. +19 −0 rack/hello_app.rb
  4. +2 −0 rack/hello_app.ru
  5. +5 −0 rack/hello_with_middleware.ru
  6. +9 −0 rack/null_middleware.rb
  7. +13 −0 rack/print_env.rb
  8. +9 −0 readme.md
  9. +4 −0 sinatra/hi.rb
  10. +111 −0 sinatra/sin_wiki.rb
43 app.rb
@@ -0,0 +1,43 @@
+require 'rack'
+require 'rdiscount'
+require 'erector'
+
+class Erector::Widget
+ def markdown(s)
+ rawtext Markdown.new(s).to_html
+ end
+
+ def stylesheet(href)
+ link :rel=>"stylesheet", :type=>"text/css", :href=> href
+ end
+end
+
+class Page < Erector::Widgets::Page
+ external :style, <<-CSS
+ h1 { margin-top: 10em; }
+ h2 { margin-top: 5em;}
+h1:first-child { margin-top: 1em; }
+ CSS
+
+ def body_content
+ markdown File.read("off-the-rails.md")
+ end
+end
+
+class OffTheRails
+ def self.call(env)
+ html = Page.new.to_html
+ [200, {'Content-Type' => 'text/html'}, [html]]
+ end
+end
+
+
+if $0 == __FILE__
+ app = Rack::Builder.new do
+ use Rack::ShowExceptions
+ run OffTheRails
+ end
+
+ # Rack::Server.start(:app => app) # this is supposed to work, but doesn't
+ Rack::Handler::Thin.run app, :Port => 8888
+end
286 off-the-rails.md
@@ -0,0 +1,286 @@
+# Off The Rails: Web Apps With Rack, Sinatra, Grape, and Siesta
+
+Ruby on Rails is the most popular web application framework for Ruby.
+But it's not the only one! If you think Rails is too big, or too
+opinionated, or too anything, you might be happy to learn about the
+new generation of so-called microframeworks built on Rack. And since
+Rails 3 is itself a Rack app, you don't have to give up Rails to get
+the benefit of Sinatra routes or Grape APIs.
+
+# Who am I
+
+Alex Chaffee
+
+* Cofounder of Cohuman
+* Former principal of Pivotal Labs
+* Co-creator of Pivotal Tracker
+* Maintainer of several Open Source Projects
+ * Wrong
+ * Erector
+ * rerun
+* BTW I'm teaching a JS class starting January 27th
+ * <http://classes.blazingcloud.net> -- sign up early for discount!
+
+# What is Rails?
+
+* Based on Conventions
+ * Model-View-Controller Architecture
+ * File layout
+
+* Web Server
+ * Routes
+ * Opinionated REST pattern
+ * Controllers
+
+* Views
+ * templates (ERB, HAML etc.)
+ * "assigns" variables
+ * partials
+ * layouts
+
+* Helpers
+ * Date Helpers
+ * String Helpers
+ * Testing support
+ * ActiveSupport
+
+* ActiveRecord
+ * Models
+ * Migrations
+
+* Scripts
+ * Scaffolding
+ * Rake tasks
+ * script/*
+ * script/console
+
+* Load Path Management
+ * mostly invisible, but very important
+ * app subdir autoloading
+ * views, models, controllers automatically located for you
+ * plugins and engines
+ * "Both awesome and terrible." - Sarah Allen
+
+## Why not use Rails?
+
+* Don't like all the magic
+* Don't need migrations or scaffolding or MVC or database or...
+* Don't like the file layout
+* Don't like unencapsulated miscegeny between controllers and views
+* Too Complex (right tool for the job)
+* Performance
+
+# Rack
+
+* A Web Server Gateway Interface
+* Based on Python's WSGI
+* Sits between web server and web app (or apps)
+* Adapters exist for...
+ * Servers: Mongrel, WEBrick, FCGI, Thin, etc.
+ * Frameworks: Rails, Camping, Sinatra, Merb, etc.
+
+## Rack Apps
+
+A Rack application is an Ruby object that responds to `call`.
+It takes exactly one argument, the *environment*
+and returns an Array of exactly three values:
+The *status*,
+the *headers*,
+and the *body*. The body must respond to `each` (so can't be a String in Ruby 1.9).
+
+ class HelloApp
+ def self.call(env)
+ [200, {'Content-Type' => 'text/plain'}, ["Hello"]]
+ end
+ end
+
+## Rack Middleware
+* Not "shelf" :-(
+* "ware" is a suffix indicating a "count noun" (or "mass noun"), which in English cannot exist in singular form without a modifier like "piece of" [furniture] or "glass of" [water]. So it feels intensely awkward for native English speakers to say "a middleware".
+
+A Rack middleware is a class that is initialized with an app, plus optional arguments. It saves the app (usually in an @app instance variable) and usually calls it somewhere in its own `call` method.
+
+ # A simple Rack Middleware that prints the environment
+ class PrintEnv
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ pp env
+ @app.call(env)
+ end
+ end
+
+## rack-contrib
+
+* <https://github.com/rack/rack-contrib>
+* More middleware than you can shake a stick at
+
+## Rackup and config.ru
+
+* `rackup` is a command-line tool that launches a Rack server
+* it reads app configuration, in Rack::Builder DSL format, from `config.ru`.
+
+`hello_app.ru`:
+
+ require './hello_app'
+ require './print_env'
+ use PrintEnv
+ use Rack::ShowExceptions
+ run HelloApp
+
+## rerun
+
+* gem written by me
+* watches the file system and reruns the given app if a file changes
+
+e.g.
+
+ rerun rackup hello_app.ru
+
+## Rack::Request
+
+Very useful object that's built from an env
+
+ * params
+ * etc.
+
+# Mixing Apps
+
+* `Rack::Builder`
+* `Rack::URLMap` dispatches to separate apps based on path
+ * e.g. `Rack::URLMap.new("/app1" => AppOne.new, "/app2" => AppTwo.new)`
+* `Rack::Cascade` tries an request on several apps, and returns the first response that is not 404 (or in a list of configurable status codes).
+
+## Testing Rack Apps
+
+### rack/mock
+
+ * Rack::MockRequest
+ * Rack::MockResponse
+ * no sessions or cookie management
+
+### Rack::Test
+
+`include Rack::Test::Methods`
+ gives your tests a DSL for web conversations
+
+ :request,
+ :get,
+ :post,
+ :put,
+ :delete,
+ :head,
+ :follow_redirect!,
+ :header,
+ :set_cookie,
+ :clear_cookies,
+ :authorize,
+ :basic_authorize,
+ :digest_authorize,
+ :last_response,
+ :last_request
+
+# Sinatra
+
+A web framework built on Rack.
+
+## Hello World
+
+ require 'sinatra'
+ get '/hi' do
+ "Hello World!"
+ end
+
+ ruby hello.rb
+
+## Sinatra Routes
+
+ post '/foo' do
+ create_foo(params)
+ end
+
+ get '/foo/:id' do
+ read_foo(params[:id])
+ end
+
+ put '/foo/:id' do
+ update_foo(params[:id])
+ end
+
+ delete '/foo/:id' do
+ delete_foo(params[:id])
+ end
+
+Note: there is no "controller" in Sinatra -- just an application and routes (aka handlers).
+
+## Sinatra DSL Features
+
+* Settings
+ * `set :sessions, true`
+* Init blocks
+ * `configure do ... end` happen at app startup
+ * `before do...end` happen before each request and can modify the request and response
+ * `after do...end` happen after each request and can modify the request and response
+* Error handling
+ * `halt 404`
+ * `not_found do...end` for 404s
+ * `error do...end` for exceptions
+ * `error 403 do...end` for status codes
+* Passing
+ * from one route handler to the next
+
+## More Sinatra Features
+
+* Sinatra apps are middleware, so you can wrap a Rails app inside a Sinatra app
+ * e.g. make your landing page and marketing site a Sinatra app, but pass logged-in users to your Rails app
+* `class MyApp < Sinatra::Base` for more modularity
+ * though you still can't break your app up into multiple classes
+ * workaround: use 'load' and have sub-files reopen MyApp
+* Sessions (via Rack::Session)
+
+## SinWiki: a simple Sinatra MVC app
+
+SinWiki is a Sinatra app I whipped up for this talk. It uses a model (domain object) that is really just an in-memory hash table, as a standin for a "real" persistent object. It has Sinatra routes for the standard REST/CRUD methods, each of which ends with an inline Erector view. (The views should probably be externalized into classes.)
+
+## Vegas: Sinatra has a backup band
+
+* Vegas is a sample app skeleton I wrote in 2009
+* Adds features to Sinatra to bring it to parity with Rails
+ * Load Path management
+ * ActiveRecord integration
+ * rake tasks for server management and deployment
+* Cohuman is based on Vegas (though heavily modified)
+
+# Grape
+
+* Like Sinatra for API apps
+ * authentication
+ * opinionated path
+
+# Siesta
+
+* Experimental controllerless framework
+* Any object can become a RESTful resource
+ * If that object is a view, it gets GET
+ * If it's persistent, it gets GET, POST, DELETE, /new, etc.
+ * Siesta handles core functionality and hands off to Handlers and Commands and Views for specialized work
+
+* Sinatra apps are also middleware, so you can chain them
+
+## Mounting Rack apps inside a Rails app
+
+* in `routes.rb`: `mount MyApp.new, :at => '/myapp`
+* Probably more to it :-)
+
+# References
+
+* [Yehuda's #10 Favorite Thing About Ruby](http://yehudakatz.com/2009/08/24/my-10-favorite-things-about-the-ruby-language/)
+* <http://rack.rubyforge.org/>
+* <http://sinatrarb.com>
+* <https://github.com/intridea/grape>
+* <https://github.com/alexch/vegas>
+* <https://github.com/alexch/siesta>
+* <https://github.com/alexch/rerun>
+* <https://github.com/bkerley/cans/>
19 rack/hello_app.rb
@@ -0,0 +1,19 @@
+class HelloApp
+ def self.call(env)
+ [200, {'Content-Type' => 'text/plain'}, ["Hello, Cleveland!!!"]]
+ end
+end
+
+# Alternative to "rackup hello_app.ru" so you can just run this file directly
+if $0 == __FILE__
+ require 'rack'
+ require './print_env'
+ app = Rack::Builder.new do
+ use PrintEnv
+ use Rack::ShowExceptions
+ run HelloApp
+ end
+
+ # Rack::Server.start(:app => app) # this is supposed to work, but doesn't
+ Rack::Handler::Thin.run app
+end
2 rack/hello_app.ru
@@ -0,0 +1,2 @@
+require './hello_app'
+run HelloApp
5 rack/hello_with_middleware.ru
@@ -0,0 +1,5 @@
+require './hello_app'
+require './print_env'
+use PrintEnv
+use Rack::ShowExceptions
+run HelloApp
9 rack/null_middleware.rb
@@ -0,0 +1,9 @@
+class NullMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ end
+end
13 rack/print_env.rb
@@ -0,0 +1,13 @@
+require 'pp'
+
+# A simple Rack Middleware that prints the environment
+class PrintEnv
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ pp env
+ @app.call(env)
+ end
+end
9 readme.md
@@ -0,0 +1,9 @@
+# Off The Rails: Web Apps With Rack, Sinatra, Grape, and Siesta
+
+This is a talk and related code written by Alex Chaffee about Rack apps and Ruby webapp frameworks that are *not* on Rails.
+
+`off-the-rails.md` is the markdown document of the "slides" (more like notes) for the presentation.
+
+`app.rb` is a little Rack app that renders the slides into HTML and wraps them in a page with some CSS. Run it with `ruby app.rb`
+
+`rack/` and `sinatra/` contain sample apps that are referenced by the talk.
4 sinatra/hi.rb
@@ -0,0 +1,4 @@
+require 'sinatra'
+get '/hi' do
+ "Hello World!"
+end
111 sinatra/sin_wiki.rb
@@ -0,0 +1,111 @@
+require 'sinatra'
+require 'erector'
+
+include Erector::Mixin
+
+# a simple in-memory wiki
+class Wiki
+ # singleton pattern
+ def self.instance
+ @instance ||= new
+ end
+
+ def initialize
+ @pages = {}
+ @next_id = 1
+ end
+
+ def add(name, body)
+ id = @next_id
+ @next_id += 1
+ @pages[id] = Page.new(id, name, body)
+ id
+ end
+
+ def get(id)
+ @pages[id.to_i]
+ end
+
+ def pages
+ @pages.values
+ end
+end
+
+class Page < Struct.new(:id, :name, :body)
+end
+
+class SinWiki < Sinatra::Base
+ set :run, true
+
+ def wiki
+ Wiki.instance
+ end
+
+ get '/' do
+ erector {
+ head { title "Welcome to SinWiki" }
+ body {
+ h1 "SinWiki"
+ ul do
+ li { a "new page", :href => "/page/new" }
+ wiki.pages.each do |page|
+ li { a page.name, :href => "/page/#{page.id}"}
+ end
+ end
+ }
+ }
+ end
+
+ get "/page/new" do
+ erector {
+ head { title "SinWiki: New Page" }
+ body {
+ h1 "SinWiki: New Page"
+
+ form :method => "post", :action => "/page" do
+ table do
+ tr do
+ td "Name"
+ td { input :name => "name" }
+ end
+ tr do
+ td "Body"
+ td { textarea :name => "body" }
+ end
+ tr do
+ td ""
+ td { input :type => "submit" }
+ end
+ end
+ end
+ }
+ }
+ end
+
+ post "/page" do
+ id = wiki.add(params[:name], params[:body])
+ redirect "/page/#{id}"
+ end
+
+ get "/page/:id" do |id|
+ page = wiki.get(id)
+ if page.nil?
+ halt 404, "#{request.path} not found"
+ end
+
+ erector {
+ head { title "SinWiki: #{page.name}" }
+ body {
+ h1 "SinWiki"
+ h2 page.name
+ p page.body
+ a "index", :href => "/"
+ }
+ }
+ end
+
+end
+
+if $0 == __FILE__
+ SinWiki.run! :host => 'localhost', :port => 9090
+end

0 comments on commit 678db79

Please sign in to comment.
Something went wrong with that request. Please try again.