Microsoft's Internet Explorer browser has no built-in vector graphics machinery required for "loss-free" gradient background themes.

Please upgrade to a better browser such as Firefox, Opera, Chrome, Safari or others with built-in vector graphics machinery and much more. (Learn more or post questions or comments at the Slide Show (S9) project site. Thanks!)

Going Camping – A Web Microframework in 4k Ruby

Jeremy McAnally @ GoRuCo (Gotham Ruby Conference) 2007, New York City

Adapted S6/S91 Version from Original PDF Slide Deck


1 (Source)

Slide Show Keyboard Controls (Help)

Action Key
Go to next slide Space Bar, Right Arrow, Down Arrow, Page Down
Go to previous slide Left Arrow, Up Arrow, Page Up
Toggle between slideshow and outline view (Ø) T
Show/hide slide controls (Ø « ») C, Move mouse to bottom right corner

About Jeremy McAnally

What is Camping?

%w[rubygems active_record markaby metaid ostruct].each {|lib| require lib} module Camping;C=self;module Models;end;Models::Base=ActiveRecord::Base module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.detect{|x|x. scan(p).size==args.size}.dup){|str,a|str.gsub(p,(a.method(a.class.primary_key )[]rescue a).to_s)};end;def / p;File.join(@root,p) end;end;module Controllers module Base;include Helpers;attr_accessor :input,:cookies,:headers,:body, :status,:root;def method_missing(m,*args,&blk);str=m==:render ? markaview( *args,&blk):eval("markaby.#{m}(*args,&blk)");str=markaview(:layout){str }rescue nil;r(200,str.to_s);end;def r(s,b,h={});@status=s;@headers.merge!(h) @body=b;end;def redirect(c,*args);c=R(c,*args)if c.respond_to?:urls;r(302,'', 'Location'=>self/c);end;def service(r,e,m,a);@status,@headers,@root=200,{},e[ 'SCRIPT_NAME'];@cookies=C.cookie_parse(e['HTTP_COOKIE']||e['COOKIE']);cook= @cookies.marshal_dump.dup;if ("POST"==e['REQUEST_METHOD'])and %r|\Amultipart\ /form-data.*boundary=\"?([^\";,]+)\"?|n.match(e['CONTENT_TYPE']);return r(500, "No multipart/form-data supported.")else;@input=C.qs_parse(e['REQUEST_METHOD' ]=="POST"?r.read(e['CONTENT_LENGTH'].to_i):e['QUERY_STRING']);end;@body= method(m.downcase).call(*a);@headers["Set-Cookie"]=@cookies.marshal_dump.map{ |k,v|"#{k}=#{C.escape(v)}; path=/"if v != cook[k]}.compact;self;end;def to_s "Status: #{@status}\n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v| v.to_a.map{|v2|"#{k}: #{v2}"}}.flatten.join("\n")}\n\n#{@body}";end;def \ markaby;Class.new(Markaby::Builder){@root=@root;include Views;def tag!(*g,&b) [:href,:action].each{|a|(g.last[a]=self./(g.last[a]))rescue 0};super end}.new( instance_variables.map{|iv|[iv[1..-1].intern,instance_variable_get(iv)]},{}) end;def markaview(m,*args,&blk);markaby.instance_eval{Views.instance_method(m ).bind(self).call(*args, &blk);self}.to_s;end;end;class R;include Base end class NotFound<R;def get(p);r(404,div{h1("#{C} Problem!")+h2("#{p} not found") });end end;class ServerError<R;def get(k,m,e);r(500,markaby.div{h1 "#{C} Prob\ lem!";h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{e.backtrace.each{|bt|li( bt)</pre>)end end;class<<self;def R(*urls);Class.new(R){meta_def(:inherited){|c| c.meta_def(:urls){urls</pre>;end;def D(path);constants.each{|c|k=const_get(c) return k,$~[1..-1] if (k.urls rescue "/#{c.downcase}").find {|x|path=~/^#{x}\ \/?$/}};[NotFound,[path]];end end end;class<<self;def escape(s);s.to_s.gsub( /([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end;def unescape(s);s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1. delete('%')].pack('H*')} end;def qs_parse(qs,d='&;');OpenStruct.new((qs||''). split(/[#{d}] */n).inject({}){|hsh,p|k,v=p.split('=',2).map{|v|unescape(v)} hsh[k]=v unless v.empty?;hsh}) end;def cookie_parse(s);c=qs_parse(s,';,') end def run(r=$stdin,w=$stdout);w<<begin;k,a=Controllers.D "/#{ENV['PATH_INFO']}". gsub(%r!/+!,'/');m=ENV['REQUEST_METHOD']||"GET";k.class_eval{include C include Controllers::Base;include Models};o=k.new;o.service(r,ENV,m,a);rescue\ =>e;Controllers::ServerError.new.service(r,ENV,"GET",[k,m,e]);end;end;end module Views; include Controllers; include Helpers end;end

What is Camping?

What is Camping?

When to use it

Controlling Camping

Classes define actions

module Blog::Controllers
  class Index < R '/'
    def get
      # Do something fun
    end
  end
end

Define methods on those for request types (i.e., get and post)

Controlling Camping

Routes are regular expressions

class Edit < R '/(\w+)/edit'
  def get(page_name)
  end
end

The View

Views are constructed using Markaby

p "Hello, world!"
div "This is a #{word}!", :id => "example"

No more ERb!

<p>Hello, world!</p>
<div id="example">
  This is a <%= word %>!
</div>

The View

p "Hello, world!"

div :id => "example" do
  span "This is a #{word}!"
end
div :id => 'pants' do
  ul do
    ['blue', 'red', 'fancy'].each do |item|
      li item
    end
  end
end

Models

Models are just ActiveRecord classes.

class Panda
end

If you don’t know what ActiveRecord is…

my_panda = Panda.new
my_panda.name = "Randall"
my_panda.fav_food = "Dirt"
my_panda.save

Defaults to SQLite

Models

Since it’s just ActiveRecord, you can use migrations

class CreatePan < V 1.0
  def self.up
    create_table :pans do |t|
      t.column :type, :string
      t.column :name, :text
    end
  end
end

You can also use other model-enhancing stuff

module CMS::Models
  class Item < Base
    acts_as_versioned
  end
end

Putting It Together – Deployment

What if…?

But It Ain’t Rails! – ActiveSupport

"plants".singularize
# => plant

14.even?
# => true

10.megabytes
# => 10485760

Moving away from SQLite

host : 127.0.0.1
port : 3301
server : mongrel
database :
  :adapter: mysql
  :database: myapp
  :hostname: localhost
  :username: user
  :password: passw0rd!
log:
  my.log

Sessions

Like Rails, Camping has sessions built right in

require "camping/session"

module Importer
  include Camping::Session
end

# Now I can use @state to
# access sessions values

Testing

require 'mosquito'
require 'inventory'

Inventory.create
include Inventory::Models

class TestInventory < Camping::FunctionalTest

  def test_view_item
    get '/view/1'
    assert_response :success
  end
end

Form Helpers

Serving static files

Static files can be embedded in a Camping application

class Style < R '/base.css'
  def get
    @headers['Content-Type'] = 'text/css'
    "body { color: #ddd; }"
  end
end

# In our view...
link :href => R(Style), :rel => 'stylesheet', :type => 'text/css'

They can also be read from disk

class Index < R '/'
  def get
    File.read('index.html')
  end
end

Optimally, your front end web server would handle them

Before/after filters

Camping allows you to override the service method to put logic before and after it handles a request

module WikiFilter
  def service(*args)
    @value = "Hello!"
    super(*args)
    @value = nil
  end
end

module Wiki
  include WikiFilter
end

Decamper

Kindling