Goal
Show how to write a simple Rack
application responding differently if the request is a POST or a GET request. Also show how to use the map method to implement a simple router.
Code
Save the following code in a file named: rack_example.ru
class Idea
attr_accessor :title, :body, :created_at
# Memory store, gets cleared as the process is restarted
def self.store
@ideas ||= []
end
class InvalidParams < StandardError; end
# create an instance based on some passed params
def initialize(params)
raise InvalidParams, "You need to provide at least a title" unless params['title']
self.title = params['title']
self.body = params['body']
self.created_at = Time.now
end
# Converts an instance into a string
def to_s
"#{title} at #{created_at.to_s}\n#{body}"
end
end
class IdeaAPI
def call(env)
request = Rack::Request.new(env)
case request.request_method
when 'POST'
begin
idea = Idea.new(request.params)
rescue Idea::InvalidParams => error
[400, {"Content-Type" => "text/plain"}, [error. ] ]
else
Idea.store << idea
[200, {"Content-Type" => "text/plain"}, ["Idea added, currently #{Idea.store.size} ideas are in memory!"]]
end
when 'GET'
[200, {"Content-Type" => "text/plain"}, [Idea.store.map{|idea, idx| idea.to_s }.join("\n\n") + "\n"]]
else
[404, {}, ["Did you get lost?"]]
end
end
end
map '/ideas' do
run IdeaAPI.new
end
The code is pretty straight-forward, but let me walk you through it nonetheless.
We have an Idea
class which is just there for the demo. It creates an instance of itself when calling #new
with a hash of params. If the passed hash doesn't have a value for the 'title'
key, then an exception is raised.
At the bottom of the file, we can see that I'm mapping incoming requests for the '/ideas' URI to an instance of IdeaAPI
.
The run
method does what you expect, it runs the instance of the IdeaAPI
passing it the Rack environment.
The IdeaAPI
class implements the Rack protocol by providing a call(env)
method which gets triggered when the request is dispatched to an instance of itself.
The environment object is then converted into a Request
object which provides the developer with a few helpers.
Using one of these helpers, we check on the HTTP verb using the request_method
instance method on our newly-created object.
Finally using a case
statement, we can provide a response based on the HTTP verb. Note that the response follows the usual Rack response format.
Notes
- In real life, you probably don't want to raise an exception and rescue it. This is an expensive approach and should be avoided.
- Using map the way is shown doesn't prevent URIs such as '/ideas/foo' to be routed to our API. This is by design, you can also lose
map
all together and got for an approach such as: https://gist.github.com/1447478