Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Ruby

Rest Service with Ruby + Sinatra + Datamapper

4.90/5 (5 votes)
21 Oct 2013CPOL3 min read 29.2K  
First web service with Ruby, Sinatra and Datamapper

Introduction

I am going to show you how to build a web service to create, read, update and delete movies with Ruby, Sinatra and Datamapper.

This tip assumes you know how to execute an HTTP request and that you have basic knowledge about Ruby.

Technologies Used

To create this new service, we are going to use 3 useful tools that are available for free on the internet.

Ruby

The language that everybody is talking about, specially since the rails framework was released.

Sinatra

A Lightweight Web Framework for the Ruby programming language.

Datamapper

It's an Object Relational Mapper written in Ruby that allows you to access the database without pain.

Project Structure

The Sinatra framework does not have an official project structure, which means that you can play around with it and choose the structure you like the most.

With Sinatra, you can build from a simple one file website or service to a big and structured project with modules and almost everything you can think of.

The structure I created for the project is this:

Image 1

The important files in the project are:

models/movie.rb

This file is a Datamapper model, which means that there will be a table in the database containing the structure described in the file: 

# encoding: UTF-8
class Movie
  include DataMapper::Resource

  property :id,         Serial
  property :title,      String
  property :director,   String
  property :synopsis,   Text
  property :year,       Integer
end 

routes/movies.rb

The file where we define the entry points of our application. For building service address, I usually follow the rules given below:

  1. Verbs are bad in routes, nouns are always welcome. good example: http get /movies/:id, bad example: http get /getmoviesbyid/:id
  2. HTTP headers are your friends, use them for changing the response format and content-type, at the end of the day, you will have clean URLs for the service you build.
  3. Names in routes must be plural.
HTTP VERB RESOURCEACTION
get /api/moviesget all movies
get/api/movies/:idget a single movie by id
post/api/moviescreate a new movie
put/api/movies/:idupdate a specific movie by id
delete/api/movies/:iddelete a movie by id
# encoding: UTF-8

get '/api/movies' do
  format_response(Movie.all, request.accept)
end

get '/api/movies/:id' do
  movie ||= Movie.get(params[:id]) || halt(404)
  format_response(movie, request.accept)
end

post '/api/movies' do
  body = JSON.parse request.body.read
  movie = Movie.create(
    title:    body['title'],
    director: body['director'],
    synopsis: body['synopsis'],
    year:     body['year']
  )
  status 201
  format_response(movie, request.accept)
end

put '/api/movies/:id' do
  body = JSON.parse request.body.read
  movie ||= Movie.get(params[:id]) || halt(404)
  halp 500 unless movie.update(
    title:    body['title'],
    director: body['director'],
    synopsis: body['synopsis'],
    year:     body['year']
  )
  format_response(movie, request.accept)
end

delete '/api/movies/:id' do
  movie ||= Movie.get(params[:id]) || halt(404)
  halt 500 unless movie.destroy
end 

helpers/response_format.rb

This file will be in change of formatting the HTTP response in different formats depending on the HTTP Accept header.

# encoding: UTF-8
require 'sinatra/base'

module Sinatra
  module ResponseFormat
    def format_response(data, accept)
      accept.each do |type|
        return data.to_xml  if type.downcase.eql? 'text/xml'
        return data.to_json if type.downcase.eql? 'application/json'
        return data.to_yaml if type.downcase.eql? 'text/x-yaml'
        return data.to_csv  if type.downcase.eql? 'text/csv'
        return data.to_json
      end
    end
  end
  helpers ResponseFormat
end

This custom Sinatra helper allows us to respond in different formats and give the clients to change the response format they want.

main.rb

Finally, we have the file that puts all the projects together and makes it possible to run the application.

# encoding: UTF-8
require 'json'
require 'sinatra'
require 'data_mapper'
require 'dm-migrations'

configure :development do
  DataMapper::Logger.new($stdout, :debug)
  DataMapper.setup(
    :default,
    'mysql://root:12345@localhost/sinatra_service'
  )
end

configure :production do
  DataMapper.setup(
    :default,
    'postgres://postgres:12345@localhost/sinatra_service'
  )
end

require './models/init'
require './helpers/init'
require './routes/init'

DataMapper.finalize 

Running the Project

In order to be able to run the project, we need to follow a set of steps:

  1. Install the ruby gem bundler [sudo] gem install bundler
  2. Navigate to the project directory and run bundle install, you should see something like:

    Image 2

  3. Run rake migrate so the database structure gets created auto-magically.

    Image 3

  4. Run ruby main.rb

    Image 4

If you followed the steps above, you should now be serving a brand new web service.

Consuming the Web Service

For consuming the web service, you can use whatever you want, you can use curl, postman, or any other HTTP client library for your favorite programming language.

First of all, we need to create some records to test the rest of the service, so let's create some movies by executing an HTTP post request to the server.

HTTP POST

http://localhost:4567/api/movies

Request body:

JavaScript
{
  "title": "The Shawshank Redemption", 
  "director": "Frank Darabont", 
  "synopsis": "Two imprisoned men bond over a number of years, 
  finding solace and eventual redemption through acts of common decency. ", 
  "year": 1994
} 

http://localhost:4567/api/movies

Request body:

JavaScript
{
  "title": "The Dark Knight", 
  "director": "Christopher Nolan", 
  "synopsis": "When Batman, Gordon and Harvey Dent 
  launch an assault on the mob, they let the clown out of the box, 
  the Joker, bent on turning Gotham on itself and bringing any heroes down to his level.", 
  "year": 2008
}

HTTP GET

http://localhost:4567/api/movies

JavaScript
 [
    {
        "id": 1,
        "title": "The Shawshank Redemption",
        "director": "Frank Darabont",
        "synopsis": "Two imprisoned men bond over a number of years, 
        finding solace and eventual redemption through acts of common decency. ",
        "year": 1994
    },
    {
        "id": 2,
        "title": "The Dark Knight",
        "director": "Christopher Nolan",
        "synopsis": "When Batman, Gordon and Harvey Dent launch an 
        assault on the mob, they let the clown out of the box, the Joker, 
        bent on turning Gotham on itself and bringing any heroes down to his level.",
        "year": 2008
    }
] 

http://localhost:4567/api/movies/2

JavaScript
{
    "id": 2,
    "title": "The Dark Knight",
    "director": "Christopher Nolan",
    "synopsis": "When Batman, Gordon and Harvey Dent launch an assault 
    on the mob, they let the clown out of the box, the Joker, 
    bent on turning Gotham on itself and bringing any heroes down to his level.",
    "year": 2008
} 

HTTP PUT

http://localhost:4567/api/movies/2

Request body:

JavaScript
{
    "title": "The Dark Knight",
    "director": "Christopher Nolan",
    "synopsis": "Updated synopsis",
    "year": 2008
}

HTTP DELETE 

http://localhost:4567/api/movies/2

Changing the Response Format

It is possible to change the response format by specifying the Accept header, for instance, if you want to get XML back from the server, you need to specify the Accept: text/xml header in the request: 

XML Response:

curl -H "Accept: text/xml" http://localhost:4567/api/movies 

XML
<?xml version="1.0"?>
<movies type="array">
  <movie>
    <id type="integer">1</id>
    <title>The Shawshank Redemption</title>
    <director>Frank Darabont</director>
    <synopsis>Two imprisoned men bond over a number of years, 
    finding solace and eventual redemption through acts of common decency.
    </synopsis>
    <year type="integer">1994</year>
  </movie>
</movies>
 
JSON Response (default):
[
  {
    "id": 1,
    "title": "The Shawshank Redemption",
    "director": "Frank Darabont",
    "synopsis": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency. ",
    "year": 1994
  }
] 
YAML Response:

curl -H "Accept: text/x-yaml" http://localhost:4567/api/movies

---
- !ruby/DataMapper,1.2.0.rc1:Movie
  id: 1
  title: The Shawshank Redemption
  director: Frank Darabont
  synopsis: Two imprisoned men bond over a number of years, finding solace and eventual
    redemption through acts of common decency.
  year: 1994
CSV Response: 

curl -H "Accept: text/csv" http://localhost:4567/api/movies

1,The Shawshank Redemption,Frank Darabont,"Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency. ",1994

Source Code 

The source code of this project is available in a public repository at Bitbucket.

You can get it if you install git and run:

git clone https://bitbucket.org/crojasaragonez/sinatra_service.git

Points of Interest

Writing web services with Sinatra is really fast, you can create them for test and production purposes. This is just a minimum example of what you can build with Sinatra, there's a lot of good documentation on their site that is worth seeing.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)