title: Going Camping - A Web Microframework in 4k Ruby
gradient-colors: black grey
h1. Going Camping - A Web Microframework in 4k Ruby
"Jeremy McAnally":http://www.jeremymcanally.com @ GoRuCo (Gotham Ruby Conference) 2007, New York City
Adapted S6/S9[1] Version from "Original PDF Slide Deck":http://www.jeremymcanally.com/going_camping.pdf
fn1. ("Source":http://slideshow.rubyforge.org/svn/samples/camping.textile)
*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 |
h1. About Jeremy McAnally
* Still in college
* I dig writing
* "Humble Litte Ruby Book":http://infoq.com/minibooks/ruby
* "Ruby in Practice Book":http://manning.com/mcanally
h1. What is Camping?
* A web microframework
* 4 Kilobytes
* Written by _why the lucky stiff
p{font-size: 10px; color: white; background-color: black; padding: 5px; border: silver thick groove; -moz-border-radius: 11px;}.
%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 NotFounde;Controllers::ServerError.new.service(r,ENV,"GET",[k,m,e]);end;end;end
module Views; include Controllers; include Helpers end;end
h1. What is Camping?
* It uses Model-View-Controller
* Active Record for the models
** You probably know what this is.
* Markaby for the views
** In a word: Rubylicious.
* Has (or can easily have) most of the good controller goodness that Rails has.
h1. What is Camping?
* Everything typically lives in one file
* This file has modules inside of it for each piece of the pie
* File must be named for the head module
* @Camping.goes@
h1. When to use it
* When Rails is too fat
* Right tool for the right job
* You ain’t gonna need it!
h1. 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@])
h1. Controlling Camping
Routes are regular expressions
{{{
class Edit < R '/(\w+)/edit'
def get(page_name)
end
end
}}}
h1. The View
Views are constructed using Markaby
{{{
p "Hello, world!"
div "This is a #{word}!", :id => "example"
}}}
No more ERb!
{{{
Hello, world!
This is a <%= word %>!
}}}
h1. The View
* Tags are constructed using Ruby methods
* Blocks build the tag hierarchy
* Attributes are fed as parameters
{{{
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
}}}
h1. 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
h1. 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
}}}
h1. Putting It Together - Deployment
* Deploying a Camping application is as easy or easier than deploying a Rails application
* It can be deployed in a wide array of environments...
** Simple Camping server way
** "Standard" proxied deployment
** Probably others
h1. What if...?
* "What if my application gets big and unwieldy?"
* You can break it into separate files...
** Just require them and include the modules
** If it gets this big though, think about Rails
h1. But It Ain't Rails! - ActiveSupport
* You can pull a lot of Rails over to Camping
* ActiveSupport for example...
{{{
"plants".singularize
# => plant
14.even?
# => true
10.megabytes
# => 10485760
}}}
h1. Moving away from SQLite
* Camping defaults to SQLite for its database
* BUT! You can use other RDBMS options.
{{{
host : 127.0.0.1
port : 3301
server : mongrel
database :
:adapter: mysql
:database: myapp
:hostname: localhost
:username: user
:password: passw0rd!
log:
my.log
}}}
h1. 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
}}}
h1. Testing
* Use Mosquito for bugfree Camping
* Not in the default package, but still available
{{{
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
}}}
h1. Form Helpers
* Gregory Brown posted a nice snippet to get a @form@ equivalent in Camping
* I'm currently working on extending that to a usable @form_for@
h1. 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
h1. 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
}}}
h1. Decamper
* A little application to convert your Camping application to a Rails application
* Status is unknown
h1. Kindling
* A new library by me that takes the top 5-10 "Railsisms" and lets you use them in Camping
* Currently supports...
** Easy before/after filters
** Static file download/upload
** Easy addition of template handlers, with default support for ERb