Camping

The Serious Joke

Trotter Cashion

I Thought Camping Only Applied To Boyscouts

The Tent

Shrink your font!

%w[rubygems active_record markaby metaid tempfile uri].each{|l|require l}
module Camping;C=self;F=__FILE__;S=IO.read(F).gsub(/_+FILE_+/,F.dump)
module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.find{|x|x.scan(p).
size==args.size}.dup){|str,a|str.sub(p,(a.__send__(a.class.primary_key)rescue
a).to_s)} end;def URL c='/',*a;c=R(c,*a)if c.respond_to?:urls;c=self/c;c=
"http://"+@env.HTTP_HOST+c if c[/^\//];URI(c) end;def / p;p[/^\//]?@root+p:p end
def errors_for o;ul.errors{o.errors.each_full{|x|li x}}if o.errors.any? end end
module Base;include Helpers;attr_accessor :input,:cookies,:env,:headers,:body,
:status,:root;def method_missing m,*a,&b;s=m==:render ? markaview(*a,&b):eval(
"markaby.#{m}(*a,&b)");s=markaview(:layout){s} if Views.method_defined?:layout
r 200,s.to_s end;def r s,b,h={};@status=s;@headers.merge!(h);@body=b end;def 
redirect *a;r 302,'','Location'=>URL(*a) end;def initialize r,e,m;e=H.new e
@status,@method,@env,@headers,@root=200,m.downcase,e,{'Content-Type'=>"text/htm\
l"},e.SCRIPT_NAME[0...-1]+e.SCRIPT_NAME[-1, 1].delete('/')
@ck=C.kp e.HTTP_COOKIE;q=C.qs_parse e.QUERY_STRING;if "post"==
@method;@in=r.read e.CONTENT_LENGTH.to_i;if %r|\Amultipart/form-data.*boundary\
=\"?([^\";,]+)|n.match e.CONTENT_TYPE;b="--#$1";@in.split(/(?:\r?\n|\A)#{Regexp.
quote(b)}(?:--)?\r\n/m).map{|y|h,v=y.split "\r\n\r\n",2;fh={};[:name,:filename].
map{|x|fh[x]=$1 if h=~/^Content-Disposition: form-data;.*(?:\s#{x}="([^"]+)")/m}
fn=fh[:name];if fh[:filename];fh[:type]=$1 if h=~/^Content-Type:(.+?)(\r\n|\Z)/m
fh[:tempfile]=Tempfile.new(:C).instance_eval{binmode;write v;rewind;self}else
fh=v end;q[fn]=fh if fn}else q.merge! C.qs_parse(@in) end end;@cookies,@input=
@ck.dup,q.dup end;def service *a;@body=send(@method,*a)if respond_to?@method
@headers["Set-Cookie"]=@cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/"/"}\
" if v != @ck[k]}.compact;self end;def to_s;"Status: #{@status}\n#{@headers.map{
|k,v|[*v].map{|x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}" end;def markaby;Mab.new(
instance_variables.map{|iv|[iv[1..-1],instance_variable_get(iv)]}) end;def 
markaview m,*a,&b;h=markaby;h.send m,*a,&b;h.to_s end end;class R;include Base
end;module Controllers;class NotFound;def get p;r(404,div{h1 "Cam\ping Problem!"
h2 p+" not found"}) end end;class ServerError;include Base;def get k,m,e;r(500,
Mab.new{h1 "Cam\ping Problem!";h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{
e.backtrace.each{|bt|li(bt)}}}.to_s)end end;class<x;Controllers::ServerError.new(r,e,'get').service(
k,m,x)end end;module Views;include Controllers,Helpers end;module Models;A=
ActiveRecord;Base=A::Base;def Base.table_name_prefix;"#{name[/^(\w+)/,1]}_".
downcase.sub(/^(#{A}|camping)_/i,'')end end;class Mab

What Is It Good For?

So How Does It Work?

Our file:

['rubygems', 'camping', 'mongrel', 'mongrel/camping'].each {|f| require f}
Camping.goes :SuperApp
  

Camping.goes

Models via ActiveRecord

module SuperApp::Models
  def self.schema(&block); @@schema = block if block_given?; @@schema; end
  class Quote < Base; end
end
  

Controlling the Camp

module SuperApp::Controllers
  class Index < R '/'
    def get; @quotes = Quote.find_all; render :index; end
    def post; puts input.inspect; Quote.create(:stuff => input.stuff)
      @quotes = Quote.find_all; render :index; end
  end
end
  

Observing Our Tarp

module SuperApp::Views
  def layout; html { body { self << yield } }; end
  def index; @quotes.map{ |q| p { q.stuff }}.join(''); _form; end

  def _form; form :action => R(Index), :method => 'post' do
      input :name => 'stuff', :type => 'text'; br
      input :type => 'submit', :name => 'enter', :value => 'Submit'
    end
  end
end
  

The Whole Thing

['rubygems', 'camping', 'mongrel', 'mongrel/camping'].each {|f| require f}
Camping.goes :SuperApp
module SuperApp::Models
  def self.schema(&block); @@schema = block if block_given?; @@schema; end
  class Quote < Base; end
end
module SuperApp::Controllers
  class Index < R '/'
    def get; @quotes = Quote.find_all; render :index; end
    def post; puts input.inspect; Quote.create(:stuff => input.stuff)
      @quotes = Quote.find_all; render :index; end
  end
end
module SuperApp::Views
  def layout; html { body { self << yield } }; end
  def index; @quotes.map{ |q| p { q.stuff }}.join(''); _form; end

  def _form; form :action => R(Index), :method => 'post' do
      input :name => 'stuff', :type => 'text'; br
      input :type => 'submit', :name => 'enter', :value => 'Submit'
    end
  end
end

SuperApp::Models.schema do
  create_table :superapp_quotes, :force => true do |t|
    t.column :id, :integer, :null => false
    t.column :stuff, :string, :null => false
  end
end

def SuperApp.create
  #unless SuperApp::Models::Quote.table_exists?
    ActiveRecord::Schema.define(&SuperApp::Models.schema)
  #end
end

if __FILE__ == $0
  ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => 'quotes.db'
  SuperApp::Models::Base.logger = Logger.new('camping.log')
  SuperApp::Models::Base.threaded_connections=false
  SuperApp.create 
  
  server = Mongrel::Camping.start('0.0.0.0', 5000, '/camping', SuperApp)
  puts "Access your app at http://localhost:5000/camping"
  server.acceptor.join
end