All I Need to Know I Learned by Writing My Own Web Framework

Post on 05-Sep-2014

6.483 views 0 download

Tags:

description

Slides from session at Rubyconf 2008

Transcript of All I Need to Know I Learned by Writing My Own Web Framework

All I Really Need to Know* I Learned by Writing My Own Web Framework

7 November 2008Rubyconf Ben Scofield

* about Ruby and the web

Rails

your customframework

Rails

Starter Projects

Hello World

main() { printf("hello, world\n");}

-module(hello).

-export([hello/0]).

hello() -> io:format("Hello World!~n", []).

PROGRAM HELLO PRINT*, 'Hello World!' END

!greeting.+!greeting : true <- .print("Hello World").

Imports System.Console

Class HelloWorld

Public Shared Sub Main() WriteLine("Hello, world!") End Sub

End Class

main = putStrLn "Hello World"

Applications

To-do lists

DIY Blog

Frameworks?

PHP Framework (based on Rails)

NOT FOR PRODUCTION!

well, maybe for production

but really:

NOT FOR PRODUCTION!

Frameworks

ActionMailerActionPackActiveRecordActiveResourceActiveSupport

Railties

Ruby on Rails

request => response

SequelActiveRecordDataMapper

Og

persistence layer

ERBLiquidAmrita2ErubisMarkabyHAML

templating layer

RamazeWaves

ActionPackMerb CoreSinatraCamping

the middle layer

ActionMailerMerb HelpersActiveSupport

Railties

utilities

Tools

Rack

MackCosetCampingHalcyonMavericSinatraVintageRamazeWavesMerb

CGISCGILSWS

MongrelWEBrickFastCGIFuzedThinEbb

mod_rack

Rack::LintRack::URLMapRack::Deflater

Rack::CommonLoggerRack::ShowExceptions

Rack::ReloaderRack::StaticRack::Cache

middlewares

Rack::RequestRack::Responseutility

Other Layers

SequelActiveRecordDataMapper

Og

persistence layer

ERBLiquidAmrita2ErubisMarkabyHAML

templating layer

Start at the End

Vision

REST and Resources

fully-formed web applicationsbuilt on resources

fully-formed web applications

built on resources

Birth of Athena

Dionysusask me after if you don’t get the joke

Project

class Habit < Athena::Resource def get @habit = Habit.find(@id) end # ...end

RouteMap = { :get => { /\/habit\/new/ => {:resource => :habit, :template => :new}, /\/habit\/(\d+)/ => {:resource => :habit}, /\/habit\/(\d+)\/edit/ => {:resource => :habit, :template => :edit} }, :put => { /\/habit\/(\d+)/ => {:resource => :habit} }, :delete => { /\/habit\/(\d+)/ => {:resource => :habit} }, :post => { /\/habit\// => {:resource => :habit} }}

Results

puts "Starting Athena application"

require 'active_record'require File.expand_path(File.join('./vendor/athena/lib/athena'))puts "... Framework loaded"

Athena.require_all_libs_relative_to('resources')puts "... Resources loaded"

use Rack::Static, :urls => ['/images', '/stylesheets'], :root => 'public'run Athena::Application.newputs "... Application started\n\n"

puts "^C to stop the application"

module Athena class Application def self.root File.join(File.dirname(__FILE__), '..', '..', '..', '..') end def self.route_map @route_map ||= { :get => {}, :post => {}, :put => {}, :delete => {} } @route_map end def call(environment) request = Rack::Request.new(environment) request_method = request.params['_method'] ? # ... matching_route = Athena::Application.route_map # ...

if matching_route resource = matching_route.last[:class].new(request, request_method) resource.template = matching_route.last[:template] return resource.output else raise Athena::BadRequest end end endend

module Athena class Resource extend Athena::Persistence attr_accessor :template def self.inherited(f) unless f == Athena::SingletonResource url_name = f.name.downcase routing = url_name + id_pattern url_pattern = /^\/#{routing}$/

Athena::Application.route_map[:get][/^\/#{routing}\/edit$/] = # ... Athena::Application.route_map[:post][/^\/#{url_name}$/] = # ... # ... end end def self.default_resource Athena::Application.route_map[:get][/^\/$/] = {:class => # ... end def self.id_pattern '\/(\d+)' end def get; raise Athena::MethodNotAllowed; end def put; raise Athena::MethodNotAllowed; end def post; raise Athena::MethodNotAllowed; end def delete; raise Athena::MethodNotAllowed; end

# ... endend

class Habit < Athena::Resource persist(ActiveRecord::Base) do validates_presence_of :name end

def get @habit = Habit.find_by_id(@id) || Habit.new_record end def post @habit = Habit.new_record(@params['habit']) @habit.save! end def put @habit = Habit.find(@id) @habit.update_attributes!(@params['habit']) end def delete Habit.find(@id).destroy endend

class Habits < Athena::SingletonResource default_resource def get @habits = Habit.all end def post @results = @params['habits'].map do |hash| Habit.new_record(hash).save end end def put @results = @params['habits'].map do |id, hash| Habit.find(id).update_attributes(hash) end end def delete Habit.find(:all, :conditions => ['id IN (?)', @params['habit_ids']] ).map(&:destroy) endend

module Athena module Persistence def self.included(base) base.class_eval do @@persistence_class = nil end end

def persist(klass, &block) pklass = Class.new(klass)

pklass.class_eval(&block) pklass.class_eval "set_table_name '#{self.name}'.tableize" eval "Persistent#{self.name} = pklass" @@persistence_class = pklass @@persistence_class.establish_connection( YAML::load(IO.read(File.join(Athena::Application.root, # ... ) end def new_record(*args) self.persistence_class.new(*args) end def persistence_class @@persistence_class end

# ...

Lessons About the Web

HTTP status codeshttp://thoughtpad.net/alan-dean/http-headers-status.gif

207Multi-Status

207Multi-Status

sucks

rack::cachehttp://tomayko.com/src/rack-cache/

Lessons About Ruby

module Athena class Application # ...

def process_params(params) nested_pattern = /^(.+?)\[(.*\])/ processed = {} params.each do |k, v| if k =~ nested_pattern scanned = k.scan(nested_pattern).flatten first = scanned.first last = scanned.last.sub(/\]/, '') if last == '' processed[first] ||= [] processed[first] << v else processed[first] ||= {} processed[first][last] = v processed[first] = process_params(processed[first]) end else processed[k] = v end end processed.delete('_method') processed end

# ... endend Form Parameters

module Athena module Persistence def self.included(base) base.class_eval do @@persistence_class = nil end end

def persist(klass, &block) pklass = Class.new(klass)

pklass.class_eval(&block) pklass.class_eval "set_table_name '#{self.name}'.tableize" eval "Persistent#{self.name} = pklass" @@persistence_class = pklass @@persistence_class.establish_connection( YAML::load(IO.read(File.join(Athena::Application.root, # ... ) end def new_record(*args) self.persistence_class.new(*args) end def persistence_class @@persistence_class end

# ...

Dynamic class creation

module Athena module Persistence # ... def method_missing(name, *args) return self.persistence_class.send(name, *args) rescue ActiveRecord::ActiveRecordError => e raise e rescue super end endend

Exception Propagation

this session has been a LIE

http://github.com/bscofield/athena(under construction)

Thank you!Questions?

Ben ScofieldDevelopment Director, Viget Labs

http://www.viget.com/extend | http://www.culann.comben.scofield@viget.com | bscofield@gmail.com