How to write a Ruby and Rails 3 REST API

Background

I’ve always wondered how I’d go about publishing a real REST API on the web to do something. In this example, we’ll create an employee manager app-thing. It’s not particularly interesting but it shows what “API” means. In another bit, we’ll create an “API” meaning a library to interact with this web service.

The rails app

First, create a new rails app.
rails new rest_api

Edit config/database.yml:

  # MySQL
  #   gem install mysql2
  development:
    adapter: mysql2
    host: localhost
    database: rest_api
    username: rest_api
    password: rest_api
    pool: 5
    timeout: 5000

Install the mysql gem if you haven’t already. Optionally, create an RVM gemset for this project if you want to keep your gems clean.
gem install mysql2

 

mysql> create database rest_api;
Query OK, 1 row affected (0.00 sec)

mysql> grant all on rest_api.* to 'rest_api'@'localhost'
identified by 'rest_api';
Query OK, 0 rows affected (0.08 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.04 sec)

mysql> quit

Edit Gemfile to read only this:

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'mysql2'

Generate some default UI with scaffolding.
rails g scaffold employee name:string extension:integer

Create our database tables.
rake db:migrate

Ok, we’re going to pretty up the scaffold here. This is completely optional but I just hate the default.
Create public/stylesheets/rest_api.css

body { background-color: #5f7395; color: #333; }

body, p, ol, ul, td {
  font-family: verdana, arial, helvetica, sans-serif;
  font-size:   13px;
  line-height: 18px;
}

pre {
  background-color: #eee;
  padding: 10px;
  font-size: 11px;
}

a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }

#main {
	background-color: #fff;
	border: solid #000 1px;
	margin: 5em;
	height: 30em;
	padding: 1em;
}

#notice {
	background-color: #e1facf;
	border: solid #97C36d 1px;
	padding: 0.5em;
}

Change app/view/layouts/application.html.erb to be:

  <!DOCTYPE html>
  <html>
  <head>
    <title>RestApi</title>
    <%= stylesheet_link_tag 'rest_api' %>
    <%= javascript_include_tag :defaults %>
    <%= csrf_meta_tag %>
  </head>
  <body>

    <div id="main">

    <%= yield %>

    </div>

  </body>
  </html>

Start rails.
rails s

Browse to http://localhost:3000/employees/. Click Create New Employee and create some records. There’s no validations or anything fancy yet so just fill in both fields accurately. Now you have some data to work with that should look something like this:

CRUD with curl

Crud is Create, Read, Update, Delete. I’ll walk through each verb with curl first.

Create
This will create a new employee using curl. Create a new file called new.xml:

<?xml version="1.0" encoding="UTF-8"?>
<employee>
  <extension type="integer">3456</extension>
  <name>Randy Rhodes</name>
</employee>

curl -v -H "Content-Type: application/xml; charset=utf-8" --data-ascii @new.xml http://localhost:3000/employees.xml

Now you have a new entry in the database. You can refresh the rails /employees URL listing to watch it change.

Read
Get all employees:
curl http://localhost:3000/employees.xml

Get one employee:
curl http://localhost:3000/employees/1.xml

These will just return XML to the screen. We’ll do something with this in a bit.

Update
Create update.xml:

<?xml version="1.0" encoding="UTF-8"?>
<employee>
  <extension type="integer">7890</extension>
  <id type="integer">1</id>
  <name>Fairy Faucet</name>
</employee>

curl -v -H "Content-Type: application/xml; charset=utf8" -T update.xml http://localhost:3000/employees/1.xml

Make sure that you have an ID of 1 in your database, otherwise you’ll get a 404 or some rails rendered error message.

Delete
I assume that you have an ID of 3 in your database. In that case, the user’s URL for the controller action show() is /3 so we can send an HTTP delete to that URL like this.

curl --request DELETE http://localhost:3000/employees/3.xml

The record will be magically gone from the database now.

Ruby API Client

Now let’s make this less bound to curl. Let’s write a class called Api that represents a gem or ruby library that does some work. In our case, it’s going to make web calls (not DB calls) to update employees exactly how we were doing it with curl.

Create a new file in the rest_api/lib directory called api.rb:

require 'net/http'

class Api
  attr_accessor :url
  attr_accessor :uri

  def initialize
    @url = "http://localhost:3000/employees"
    @uri = URI.parse @url
  end

  # Create an employee using a predefined XML template as a REST request.
  def create(name="Default Name", extension="9999")
    xml_req =
    "<?xml version='1.0' encoding='UTF-8'?>
    <employee>
      <extension type='integer'>#{extension}</extension>
      <name>#{name}</name>
    </employee>"

    request = Net::HTTP::Post.new(@url)
    request.add_field "Content-Type", "application/xml"
    request.body = xml_req

    http = Net::HTTP.new(@uri.host, @uri.port)
    response = http.request(request)

    response.body
  end

  # Read can get all employees with no arguments or
  # get one employee with one argument.  For example:
  # api = Api.new
  # api.read 2 => one employee
  # api.read   => all employees
  def read(id=nil)
    request = Net::HTTP.new(@uri.host, @uri.port)

    if id.nil?
      response = request.get("#{@uri.path}.xml")
    else
      response = request.get("#{@uri.path}/#{id}.xml")
    end

    response.body
  end

  # Update an employee using a predefined XML template as a REST request.
  def update(id, name="Updated Name", extension=0000)
    xml_req =
    "<?xml version='1.0' encoding='UTF-8'?>
    <employee>
      <extension type='integer'>#{extension}</extension>
      <id type='integer'>#{id}</id>
      <name>#{name}</name>
    </employee>"

    request = Net::HTTP::Put.new("#{@url}/#{id}.xml")
    request.add_field "Content-Type", "application/xml"
    request.body = xml_req

    http = Net::HTTP.new(@uri.host, @uri.port)
    response = http.request(request)

    # no response body will be returned
    case response
    when Net::HTTPSuccess
      return "#{response.code} OK"
    else
      return "#{response.code} ERROR"
    end
  end

  def delete(id)
    request = Net::HTTP::Delete.new("#{@url}/#{id}.xml")
    http = Net::HTTP.new(@uri.host, @uri.port)
    response = http.request(request)

    # no response body will be returned
    case response
    when Net::HTTPSuccess
      return "#{response.code} OK"
    else
      return "#{response.code} ERROR"
    end
  end

end

This program is just like curl except we’re able to programmatically be more precise with what we’re querying and deleting. However, you’ll notice that the XML document is hardcoded in the program. So it’s not infinitely flexible. If you’re nodes are not named employees then this isn’t going to work so well. But this is just an example.

Now we’ll create a program to use api.rb. You’ll need nokogiri. So do:
gem install nokogiri

This program will be a rest client that will use our api class. This api could be something you’ve published and this could be how you’d document the use of your gem to the world in your README.

Create a file named api_client.rb in the root of the rest_api rails app.

require './lib/api.rb'
require 'nokogiri'

# CRUD example with an api

def list_employees(api_object)
  puts "Current Employees:"
  doc = Nokogiri::XML.parse api_object.read
  names = doc.xpath('employees/employee/name').collect {|e| e.text }
  puts names.join(", ")
  puts ""
end

api = Api.new
list_employees(api)

# Create
puts "Creating someone..."
api.create "Bobby Flay", 1999
list_employees(api)

# Read one and do nothing with it
api.read 1

# Read all and get valid IDs
doc = Nokogiri::XML.parse api.read
ids = doc.xpath('employees/employee/id').collect {|e| e.text }

# Update last record
puts "Updating last record ..."
api.update ids.last, "Robert Flaid", 2001
list_employees(api)

# Delete
puts "deleting last record ..."
api.delete ids.last
list_employees(api)

Now run with ruby api_client.rb and you should see:

Current Employees:
Fairy Faucet, Sandy Salt

Creating someone…
Current Employees:
Fairy Faucet, Sandy Salt, Bobby Flay

Updating last record …
Current Employees:
Fairy Faucet, Sandy Salt, Robert Flaid

deleting last record …
Current Employees:
Fairy Faucet, Sandy Salt

Depending on what dummy data you put in to begin with, the output might look different.

Rdoc

Optionally, you can create Rdoc for app. Run this rake task in the rails root:

rake doc:app

If you open doc/app/Api.html, you’ll see the Rdoc from the comments above. This is especially useful when publishing an API to the world. It’ll suck in comments from your methods, in this case the api.rb file has comments over every method definition that gets turned into pretty HTML.

Wrap up

So we have published an API over the web with Rails. It’s pretty easy because the scaffolding handles the xml requests in the respond_to blocks in the controllers. But we also wrapped this API in a utility class that we were able to run from the command line. This could easily be converted to a gem and published just as the rails app could easily be pushed to Heroku and published.

This example mimics a CRUD layer for a DB so closely that you’d never do this exactly. But hopefully it illustrates how you’d make a wrapper to a web service that you don’t have direct DB access to. For me, it was interesting to see what API really means. In terms of REST and the web, it’s simply publishing methods and data over HTTP (in this case wrapped up in XML). In terms of a library or gem, it means giving someone an object and methods to do something inside a black

See also REST with PHP :

https://lchandara.wordpress.com/2011/08/13/create-a-rest-api-with-php/

Reference : http://squarism.com/2011/04/01/how-to-write-a-ruby-rails-3-rest-api/

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s