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