All I Need to Know I Learned by Writing My Own Web Framework
-
Upload
ben-scofield -
Category
Technology
-
view
6.483 -
download
0
description
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
DSLsIntimidate
and Frighten
DSL
flickr: cwsteeds
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
Extractionflickr: 95579828@N00
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
Tangled classesflickr: randomurl
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
More to learnflickr: glynnis
... alwaysflickr: mointrigue
http://github.com/bscofield/athena(under construction)
Thank you!Questions?
Ben ScofieldDevelopment Director, Viget Labs
http://www.viget.com/extend | http://[email protected] | [email protected]