title: Going Camping - A Web Microframework in 4k Ruby gradient-colors: black grey
Jeremy McAnally @ GoRuCo (Gotham Ruby Conference) 2007, New York City
(Adapted S6/S9 Version from Original PDF Slide Deck)
- Still in college
- I dig writing
- Humble Litte Ruby Book
- Ruby in Practice Book
- A web microframework
- 4 Kilobytes
- Written by _why the lucky stiff
%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"][email protected]_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
- 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.
- 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
- When Rails is too fat
- Right tool for the right job
- You ain't gonna need it!
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
)
Routes are regular expressions
class Edit < R '/(\w+)/edit'
def get(page_name)
end
end
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>
- 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
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
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
- 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
- "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
- You can pull a lot of Rails over to Camping
- ActiveSupport for example...
"plants".singularize
# => plant
14.even?
# => true
10.megabytes
# => 10485760
- 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
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
- 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
- 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
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
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
- A little application to convert your Camping application to a Rails application
- Status is unknown
- 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