Restful Rabbits

Cute Rabbit

Creating RESTful Resources Using Sinatra and DataMapper

In this post, I’m going to go through how to create a resource using Sinatra and DataMapper.

What Is REST anyway?

REST stands for Representational State Transfer and was introduced by Roy Fielding in his doctoral dissertation in 2000. It is basically a way of accessing resources (these are usually objects in a database) with specific urls. It also allows you to use urls to interact with these resources (edit them for instance). For example the url /people/daz/edit would allow me to edit details about myself. The url /people/daz/delete would remove me from the database.

In this example I’m going to use rabbits as the resource, but you can adapt it to anything that needs to be stored in a database. All the data will be stored in a database using DataMapper and specific urls will be used to perform the CRUD (Create, Read, Update, Destroy) actions on each object in the database.

In REST there are usually 7 handlers/urls. This example uses 8, as there is an additional handler that allows you to give a confirmation before deleting. These roughly correspond to the CRUD actions of a database.

The handlers and associated urls are:

  • List (/rabbits) - an index page that shows all the resources
  • Show (/rabbits/1) - a page that shows an individual resource
  • New (/rabbits/new) - a form for adding a new resource
  • Create (/rabbits) - creates a new resource (no web page for this)
  • Edit (/rabbits/edit/1) - a form for editing a resource
  • Update (/rabbits/1)- updates a new resource
  • Delete Confirmation (/rabbits/delete/1) - a page that asks if you really want to delete a resource
  • Delete (/rabbits/1)- deletes the resource (no web page for this)

Notice that a number of the urls are actually the same (for example the show,update and delete urls). this is because there are actually 4 different HTTP verbs - GET,POST,UPDATE and DELETE. Sinatra is able to tell which type of request is being made and take the appropriate action. So for example /rabbits/1 will show the rabbit with an id of 1 if a GET request is made, but will delete that rabbit if a DELETE request is made.

The Code

To start with we require the necessary gems (I’m using haml for this, but it would be easy to change over to erb or something else):

require 'rubygems'
require 'sinatra'
require 'data_mapper'
require 'haml'

Next we set up the database. This line checks to see if there is a database set up on Heroku. If not then a local sqlite database is set up called rabbits.db:

DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/rabbits.db")

The next job is to create the Rabbit class (or whatever class you want to create). The first id field is pretty much necessary and gives your resource an auto-incrementing integer id that it can be referenced by. This means the database will automatically take care of assigning a unique id to each resource. The other properties are ‘name’, ‘description’, ‘age’ and ‘colour’. There are also two more fields created_at and updated_at. These take advantage of the datamapper_timestamps plugin and are automatically updated when a resource is created or updated respectively. Datamapper also has another nice plugin called datamapper_validations. This will automatically check to see if the name field is filled in (because it is required) and if age is an integer. A resource will not be created if these validation checks fail and the errors will be reported (more on that later).

class Rabbit
  include DataMapper::Resource 
  property :id,           Serial
  property :name,         String, :required => true
  property :description,  Text
  property :age,          Integer
  property :colour,       String
  property :created_at,   DateTime
  property :updated_at,   DateTime
end

Not we will create the handlers. First of all is the list handler:

# list all rabbits
get '/rabbits' do
  @rabbits = Rabbit.all
  haml :index
end

This has a simple url of ‘/rabbits’. First of all it finds all the rabbit resources in the database and keeps them in an array called @rabbits. This can then be accessed in the view called ‘index’.

Next is the new handler:

# add new rabbit
get '/rabbits/new' do
  @rabbit = Rabbit.new
  haml :new
end

This has the url of ‘rabbits/new’. It actually creates a new rabbit and keeps it the instance variable @rabbit. This is just because the form expects there to be a @rabbit object. It then displays the form in the ‘new’ view. The next handler is the create handler. This is a little more complicated and doesn’t have an associated view:

# create new rabbit   
post '/rabbits' do
  @rabbit = Rabbit.new(params[:rabbit])
  if @rabbit.save
    status 201
    redirect '/rabbits/' + @rabbit.id.to_s
  else
    status 400
    haml :new
  end
end

The first thing you might notice is that it has the same url as the list handler - ‘/rabbits’. The difference is, this handler is only accessed if the request is a post request - triggered when the new form is posted to the server. The first thing it does is create a new rabbit object using the parameters sent via the form. These are held in a hash called params:rabbit. It then checks to see if the paramaters are valid by trying to save the @rabbit object. If it does, then it returns a status of 201 (Created) and redirects to the show view so you can see the newly created rabbit. If not, then it returns a status of 400 (Bad Request) and redisplays the new view so that it can be corrected and resubmitted.

The next handler is the edit handler. The url for this is ‘/rabbits/edit/:id’, where :id is a specific integer that refers to the unique id of a particular rabbit object. This id is held in the params hash as params:id and is used in the first line of the handler to retrieve the correct rabbit from the database and store it in an object called @rabbit that can then be accessed in the ‘edit’ view.

# edit rabbit
get '/rabbits/edit/:id' do
  @rabbit = Rabbit.get(params[:id])
  haml :edit
end

When the edit page is submitted, the information from the form will be sent to the update handler, which is shown below:

# update rabbit
put '/rabbits/:id' do
  @rabbit = Rabbit.get(params[:id])
  if @rabbit.update(params[:rabbit])
    status 201
    redirect '/rabbits/' + params[:id]
  else
    status 400
    haml :edit  
  end
end

The code for updating is similar to the create handler and it also doesn’t have an associated view. First of all the rabbit with the id that was entered in the url is found in the database and stored as the @rabbit instance variable. Then it is updated using the params provided in the edit form. If this is successful, then the browser is redirected to the page that shows the rabbit (so you can see that it has been updated). If the update fails then the edit page is shown again.

The next handler is the delete confirmation page. This basically shows a page and asks you to confirm if you would like to delete the rabbit or not.

# delete rabbit confirmation
get '/rabbits/delete/:id' do
  @rabbit = Rabbit.get(params[:id])
  haml :delete
end

If you select ‘delete’ from the delete confirmation page then the delete handler, shown below is accessed. Notice that the url is the same as the update url, but the form on the delte confirmation page makes sure this is sent as a DELETE request.

# delete rabbit
delete '/rabbits/:id' do
  Rabbit.get(params[:id]).destroy
  redirect '/rabbits'  
end

This finds and removes a rabbit from the database all in one line. It then redirects the browser to the rabbits index page.

The last handler in the list is the show handler. This has to come last because Sinatra goes throught the routes in the order they appear. If this came before /rabbits/new for example then it would think that ‘new’ represented the :id parameter and try to look for a rabbit with an id of ‘new’ in the database (which obviously doesn’t exist).

# show rabbit
get '/rabbits/:id' do
  @rabbit = Rabbit.get(params[:id])
  haml :show
end

This is a fairly straightforward handler. It finds the rabbit with the correct id and stores it in the instance variable @rabbit, which can then be accessed in the associated view which is called ‘show’.

Just before the end of the file, there is a DataMapper command that will update any changes you make to the database (say if you add another property) without deleting any of the data (really easy migrations in other words):

DataMapper.auto_upgrade!

The next line signifies that it is the end of the file. This is followed by the views that correspond to some of the handlers above. The layout will be shown on all the pages. It basically contains the minimum code needed for a HTML5 webpage. All the other views will be displayed in place of = yield.

__END__
@@layout
!!! 5
%html
  %head
    %meta(charset="utf-8")
    %title Rabbits
  %body
    = yield

The first view is the index page that shows a list of all the rabbits currently in the database. These are stored in the array @rabbits. This array is cycled through to show each rabbit with a link to show, edit and delete. There is also a link at the top of this page to add a new rabbit.

@@index
%h3 Rabbits
%a(href="/rabbits/new")Create a new rabbit
- unless @rabbits.empty?
  %ul#rabbits
  - @rabbits.each do |rabbit|
    %li{:id => "rabbit-#{rabbit.id}"}
      %a(href="/rabbits/#{rabbit.id}")= rabbit.name
      %a(href="/rabbits/edit/#{rabbit.id}") EDIT
      %a(href="/rabbits/delete/#{rabbit.id}") DELETE
- else
  %p No rabbits!

The next view is used to show all of the information about a particular rabbit (stored as @rabbit). Each line represents on of the different properties. There are also links to the edit and delete pages for that particular rabbit or go back to the index.

@@show
%h3= @rabbit.name
%p Colour: #{@rabbit.colour}
%p Age: #{@rabbit.age}
%p Description: #{@rabbit.description}
%p Created at: #{@rabbit.created_at}
%p Last updated at: #{@rabbit.updated_at}
%a(href="/rabbits/edit/#{@rabbit.id}")EDIT
%a(href="/rabbits/delete/#{@rabbit.id}")DELETE
%a(href='/rabbits') Back to index

The next two views are very similar. The first is the page that is shown to add a new rabbit and the second is the page that is shown to edit a rabbit that already exists. Both of these pages start by displaying another view - ‘errors’. This is shown below and shows any errors that occurred if the form was submitted incorrectly on the previous request. The new form uses a method of POST and the edit form uses a method of PUT to ensure that the correct http verb is used. Most browsers cannot actually send a PUT request, so the trick is to ‘cheat’ and send it as a POST request (that browsers can handle) with a hidden form field that tells Sinatra that it should actually be a PUT request. Both of these views display another view called ‘form’ in the middle. This is the main form that is used for entering or editing information about the rabbits. To keep things simple, this is abstracted into its own separate view which makes things easier if it needs updating as there is only one form to change.

@@new
= haml :errors, :layout => false
%form(action="/rabbits" method="POST")
  %fieldset
    %legend Create a new rabbit
    = haml :form, :layout => false
  %input(type="submit" value="Create") or <a href='/rabbits'>cancel</a>
  
  
  
@@edit
= haml :errors, :layout => false
%form(action="/rabbits/#{@rabbit.id}" method="POST")
  %input(type="hidden" name="_method" value="PUT")
  %fieldset
    %legend Update this rabbit
    = haml :form, :layout => false
  %input(type="submit" value="Update") or <a href='/rabbits'>cancel</a>

This is the form view. It contains the correct fields for each of the properties in the database. This is shown on both the new and edit pages, so any changes made here will be shown on both of these.

@@form
%label(for="name")Name:
%input#name(type="text" name="rabbit[name]"value="#{@rabbit.name}")

%label(for="colour")Colour:
%select#quantity(name="rabbit[colour]")
  - %w[black white grey brown].each do |colour|
    %option{:value => colour, :selected => (true if colour == @rabbit.colour)}= colour
    
%label(for="description") Description:
%textarea#description(name="rabbit[description]")
  =@rabbit.description
  
%label(for="quantity") Age:
%input#age(type="text" name="rabbit[age]"value="#{@rabbit.age}")

This is the view for errors. It will only display if DataMapper reports any errors (for example if an integer is not submitted for age) and simply lists the errors that DataMapper has reported before the form is displayed, so the user can correct the errors.

@@errors
-if @rabbit.errors.any?
  %ul#errors
  -@rabbit.errors.each do |error|
    %li= error

The next view shows the delete confirmation page. This simply displays the name of the rabbit and asks if the user is sure they want to actually delete it. If they do then a form is used to submit to the delete handler. A hidden field is used to ‘fake’ a DELETE request in a similar way to the PUT request on the edit page because most browsers don’t understand this http verb either.

@@delete
%h3 Are you sure you want to delete #{@rabbit.name}?
%form(action="/rabbits/#{@rabbit.id}" method="post")
  %input(type="hidden" name="_method" value="DELETE")
  %input(type="submit" value="Delete") or <a href="/rabbits">Cancel</a>

There you have it - a nice way to create Restful resources.

You can see a working example here.

I’m working on adding some JavaScript functionality to allow resources to be created, updated and deleted using Ajax. I’d also be interested if anybody had a good example of adding a date field in the form.

Are there any really clever people who could make a generator script based on this - where you just specify the class name and it creates all the code?

This post is dedicated to Freddy the Rabbit.

blog comments powered by Disqus