Skip to content

Instantly share code, notes, and snippets.

@madx
Created July 17, 2009 11:01
Show Gist options
  • Save madx/149002 to your computer and use it in GitHub Desktop.
Save madx/149002 to your computer and use it in GitHub Desktop.
# Rackable is a tiny module that aims to provide a REST-like interface to
# any object.
#
# It uses Rack under the hood, thus any “Racked” object is a valid Rack
# application.
#
# I really don't know if this is useful, but it's more a proof of concept than
# anything.
module Rackable
attr_accessor :response, :env, :header
attr_reader :request, :data, :query
def call(env)
@env = env
@request = Rack::Request.new(env)
@response = Rack::Response.new
@header = response.header
@query = request.GET.inject({}) {|h, (k,v)| h[k.to_sym] = v; h }
@data = request.POST.inject({}) {|h, (k,v)| h[k.to_sym] = v; h }
method = env['REQUEST_METHOD'].downcase.to_sym
args = env['PATH_INFO'][1..-1].split('/').collect { |arg|
Rack::Utils.unescape(arg)
}
status, body = catch(:halt) do
begin
[200, response.write(send(method, *args))]
rescue NoMethodError
header['Allow'] = valid_methods.join(', ')
http_error 405
rescue ArgumentError
http_error 400
end
end
response.status = status
if status != 200
response.body = body
response.length = header['Content-Length'] =
Rack::Utils.bytesize(body).to_s
end
response.finish
end
private
def valid_methods
[:get, :post, :put, :delete].delete_if {|m| !respond_to?(m) }.map { |m|
m.to_s.upcase
}
end
def http_error(code)
throw :halt, [code, Rack::Utils::HTTP_STATUS_CODES[code]]
end
end
if $0 =~ /bacon$/
require 'rubygems'
require 'rack/test'
Bacon::Context.send :include, Rack::Test::Methods
class MockApp
include Rackable
def get(failing=nil)
http_error 404 if failing
"Hello, world"
end
end
def app
MockApp.new
end
describe Rackable do
it 'provides a call() method' do
app.should.respond_to :call
end
it 'calls the appropriate method on the racked object' do
get '/'
last_response.should.be.ok
last_response.body.should == app.get
end
it 'catches errors thrown inside the method' do
get '/fail'
last_response.status.should == 404
last_response.body.should == 'Not Found'
end
it 'throws a 405 when the method is not defined' do
post '/'
last_response.status.should == 405
last_response.headers['Allow'].should.not.include?('POST')
last_response.headers['Allow'].should. include?('GET')
end
it 'throws a 400 on argument errors' do
get '/foo/bar'
last_response.status.should == 400
end
end
end
require File.join(File.dirname(__FILE__), 'rackable')
class RestString
include Rackable
def initialize
@string = "Hello, world!"
end
def get()
@string
end
def put()
if data[:body]
@string << data[:body]
else
http_error 400
end
end
def delete()
@string = ""
end
end
run RestString.new
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment