Skip to content

Instantly share code, notes, and snippets.

@peter
Created January 31, 2013 19:06
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save peter/4685445 to your computer and use it in GitHub Desktop.
Save peter/4685445 to your computer and use it in GitHub Desktop.
An example of how to write a simple JSON REST API with a simplistic router directly on top of Rack (i.e. without the use of a framework like Sinatra).
#################################################################
#
# File: lib/api.rb
#
#################################################################
require 'user' # ActiveRecord model
# Connect to the db
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
class Api
def self.routes
[
{method: "GET", path: %r{^/users/(?<id>\d+)}, api_method: :find},
{method: "POST", path: %r{^/users}, api_method: :create},
{method: "PUT", path: %r{^/users/(?<id>\d+)}, api_method: :update},
{method: "GET", path: %r{^/authenticate}, api_method: :authenticate}
]
end
def self.find(params)
User.find(params[:id])
end
def self.create(params)
User.create!(params[:user])
end
def self.update(params)
User.update_attributes!(params[:user])
end
def self.authenticate(params)
User.authenticate(params[:email], params[:password])
end
end
#################################################################
#
# File: config.ru
#
#################################################################
require "rubygems"
require "bundler/setup"
require "json"
$: << File.join(File.dirname(__FILE__), "lib")
require "api"
# Simplistic router based on path and request method
class Router
def self.find_match(request_method, path, routes)
routes.each do |route|
if match = match_route(request_method, path, route)
return match
end
end
return nil
end
def self.match_route(request_method, path, route)
if route[:method] == request_method && match_data = route[:path].match(path)
route_match = route.slice(:api_method)
if match_data.names.present?
route_match[:params] = match_data.names.inject({}) { |params, name| params[name] = match_data[name]; params }
else
route_match[:params] = {}
end
route_match
else
nil
end
end
end
run(->(env) {
request = Rack::Request.new(env)
if route_match = Router.find_match(request.request_method, request.path, Api.routes)
begin
api_params = HashWithIndifferentAccess.new(request.params.merge(route_match[:params]))
response_data = Api.send(route_match[:api_method], api_params)
[200, {'Content-Type' => 'application/json'}, StringIO.new(response_data.to_json)]
rescue ActiveRecord::RecordNotFound => e
[404, {'Content-Type' => 'text/plain'}, StringIO.new(e.message)]
rescue ActiveRecord::RecordInvalid => e
[422, {'Content-Type' => 'text/plain'}, StringIO.new(e.record.full_error_messages)]
end
else
[404, {'Content-Type' => 'text/plain'}, StringIO.new('file not found')]
end
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment