Skip to main content

Crossing borders: REST on Rails

An elegant approach to Web services

Bruce Tate (bruce.tate@j2life.com), President, RapidRed
Bruce Tate
Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby on Rails. His practice now offers a full range of Ruby and Rails education, consulting, and implementation offerings.

Summary:  Earlier articles in the Crossing borders series introduced Ruby on Rails as an explosively popular framework that's serving as a catalyst for the Ruby programming language. As Ruby experiences increasing success, developers are seeking to integrate their Ruby applications with applications written in other languages. Rails provides excellent support for Web services. This article introduces Web services in Rails and focuses on a strategy known as Representational State Transfer (REST).

View more content in this series

Date:  01 Aug 2006
Level:  Intermediate
Activity:  3679 views
Comments:  

In the past 20 years, one tendency has dominated the development of commercial software tools: We love to fight complexity with complexity. Nowhere is this trend more apparent than within the distributed computing arena. The C and Java™ communities have seen some stunningly complex frameworks built to enable distributed communications. The Distributed Computing Environment (DCE) enabled remote procedure calls across applications written in C. The Common Object Request Broker Architecture (CORBA) standard enabled communications across object-oriented applications. The Enterprise JavaBeans (EJB) specification provides services for security, persistence, transactions, messaging, and remoting. Hype for each framework built to a crescendo, but each one failed to meet expectations and some were unmitigated disasters because of their complexity. Of these, only EJB 3.0, the result of a dramatic simplification overhaul, has the potential to succeed for distributed applications. The marketplace may or may not give the embattled framework another chance, and EJB will still need to deliver.

The latest massive distributed framework is Web services. Web services technology lets applications communicate with one another in a platform- and programming language-independent manner (see Resources). Web services standards too are threatened by the complexity bogeyman, but an alternative strategy known as REST promises a more straightforward approach. This article shows you how to add a REST-style Web service in Ruby on Rails and invoke the service from both Ruby and Java code.

About this series

In the Crossing borders series, author Bruce Tate advances the notion that today's Java programmers are well served by learning other approaches and languages. The programming landscape has changed since Java technology was the obvious best choice for all development projects. Other frameworks are shaping the way Java frameworks are built, and the concepts you learn from other languages can inform your Java programming. The Python (or Ruby, or Smalltalk, or ... fill in the blank) code you write can change the way that you approach Java coding.

This series introduces you to programming concepts and techniques that are radically different from, but also directly applicable to, Java development. In some cases, you'll need to integrate the technology to take advantage of it. In others, you'll be able to apply the concepts directly. The individual tool isn't as important as the idea that other languages and frameworks can influence developers, frameworks, and even fundamental approaches in the Java community.

The Web services landscape

As with EJB, CORBA, and DCE, the core abstraction for Web services is a remote procedure call. Web services use a protocol called SOAP (originally, SOAP stood for Simple Object Access Protocol, but the term is now deprecated) for expressing a message's structure with XML. Here's a hint: If a protocol starts with S for simple, it's not. The Web Services Definition Language (WSDL) provides a standard specification of a service. Like SOAP, WSDL is an involved and complicated API, and SOAP and WSDL only scratch the surface of the dozens of APIs that make up the Web services behemoth (see Resources). Web services need an overhaul, and thanks to an influential Ph.D. dissertation by Roy Fielding, they are getting one (see Resources).

Fielding's dissertation describes the REST application-networking strategy. REST is fundamentally different from full-stack Web services for three major reasons:

  • The core abstraction in REST is a remote resource instead of a remote procedure call.
  • Rather than inventing an exhaustive list of standards, REST uses existing Internet standards, including HTTP, XML, and TCP/IP.
  • Instead of every possible scenario, REST covers the most common problems.

Think of REST as browsing. REST clients use the same HTTP commands as your browser to access resources. When a REST client accesses a representation of a resource, the client transitions into a state. With various HTTP commands, REST clients can create, read, update, or delete records from a resource.

For example, take a typical blog. You get a list of posts by typing a URL such as blog.rapidred.com. Then, if you want to edit your blog entry, you type HTTP parameters on your URL (such as blog.rapidred.com/edit?article=12345), and the edit form displays. So each blog entry has its own URL, and you can read, modify, or delete content with HTTP commands by clicking a link or typing a URL directly.

In a nutshell, REST:

  • Uses TCP/IP naming standards to name resources on the Web
  • Queries and manipulates those resources with HTTP
  • Uses standard text-based message formats like XML or HTML to structure data

Ruby on Rails provides excellent support for Web services with REST.


Action Web Services overview

Rails implements Web services with a module called Action Web Services. Many development frameworks encourage a separate controller for your views and Web services. That strategy lets you maintain a consistency of style across each controller. The problem is that you need a new controller for each kind of content you serve. For example, Ajax UIs require remote XML calls to JavaScript from your controller.

Rather than dedicating a controller to your Web services, with Rails you generally use the same controller to serve content to your HTML-based views, your XML-based Web services, and your XML-based JavaScript components. The best way to understand Action Web Services is to see it in action in the context of a working application.

Create a database called service_development using your chosen database manager. Next, create a Rails project and a model with these commands:

> rails service 
> script/generate model Person

After you generate your model, you have a migration called db/migrate/001_create_people.rb. Edit that migration to look like Listing 1:


Listing 1. The migration for the people table

class CreatePeople < ActiveRecord::Migration
  def self.up
    create_table :people do |t|
      t.column :first_name, :string, :limit => 40
      t.column :last_name, :string, :limit => 40
      t.column :email, :string, :limit => 40
      t.column :phone, :string, :limit => 15
    end
  end

  def self.down
    drop_table :people
  end
end

Change the database configuration in config/database.yml to match your own database configuration and type rake migrate. Finally, generate a scaffold for a Person model and a People controller by typing script/generate scaffold Person People. You're ready to start the server with script/server. Point your browser to localhost:3000/people to see the classic Rails scaffold for Person. Figure 1 shows an application with standard Rails scaffolding:


Figure 1. A simple Rails application
Mapping frameworks

Before I introduce Web services with Rails, review the controller code. Edit app/controllers/people_controller.rb to match the code in Listing 2:


Listing 2. Controller code for PeopleController

class PeopleController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see 
http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy, :create, :update 
],
         :redirect_to => { :action => :list }

  def list
    @person_pages, @people = paginate :people, :per_page => 10
  end

  def show
    @person = Person.find(params[:id])
  end

  def new
    @person = Person.new
  end

  def create
    @person = Person.new(params[:person])
    if @person.save
      flash[:notice] = 'Person was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit
    @person = Person.find(params[:id])
  end

  def update
    @person = Person.find(params[:id])
    if @person.update_attributes(params[:person])
      flash[:notice] = 'Person was successfully updated.'
      redirect_to :action => 'show', :id => @person
    else
      render :action => 'edit'
    end
  end

  def destroy
    Person.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end


If you followed the earlier Ruby on Rails projects in this series, you know the general flow of a typical controller method:

  1. A user makes a request through HTTP by following a link or specifying a URL.
  2. The Web server directs the request to Ruby on Rails based on the domain configuration.
  3. The Rails router routes the request to the controller based on the URL pattern. The default pattern is http://host_name/controller/action/parameters.
  4. The router invokes a method on the controller with the same name as the action.
  5. The action method sets up instance variables for the view and renders the view.
  6. The action method copies any instance variables to the view.

For example, look at the show method in Listing 2. The controller sets up the @person instance variable for the view to use. Because the method doesn't specify the name of a view, Rails invokes the view with the same name as the controller action -- in this case, the view in app/views/people/show.rhtml.

Take a look at the list method. If you wanted to make this method render XML instead, you'd need to:

  • Remove the pagination
  • Convert the people instance variable to XML
  • Render XML instead of HTML

Rails makes it possible to handle the Web service and render a view from the same Web service. You don't really need pagination yet. To simplify the list method a little for the Web service, remove the pagination by making the list method in the controller look like Listing 3. You also need to remove the "Next Page" and "Previous Page" links near the bottom of the code in app/views/people/list.rhtml.


Listing 3. Simplifying list

def list
   @people = Person.find_all
end

By removing the scaffolding for pagination, you remove a feature that would make your UI more robust, but you also get something in return. You can use the same code to drive your Web service and your view. If you find that you need pagination later, you can always write some custom helpers.

Now that the base application is out of the way, you're ready to add some Web services.


Adding Web services to a Rails controller

If I wanted to be flippant, I could say, "You've already got a Web service." Remember what I said about REST? This style of Web services uses named resources. My Rails application also has named resources: host_name/people/list invokes my list service. REST-style Web services also use TCP/IP and HTTP. So does my Rails application. And well-formed HTML is a subset of XML, satisfying the last REST requirement. Just call an HTTP get on localhost:3000/people/list and parse the result to get a list of people. And that's exactly the point. REST works as the Internet works. But this is not really a REST-based Web service. Ideally, you'd like to provide an XML document that reflects the meaning of Person instead of the structure of the UI.

A real service should produce a pure data representation, one built specifically for the service's intended clients. But the sample application has two clients: end users and REST clients. To reuse the same code for both purposes, you need to give Rails more information. The Rails designers could have decided to use additional URL parameters, but mangling the URL is an ugly hack. Rails should not burden the users with such details. Instead, HTTP provides a vehicle for specifying more information: the HTTP header.

To understand the REST model for Web services, it helps to know a little more about HTTP. The curl (think of it as See URL) command lets you query a URL with a single command and see the response. Unix-based operating systems include curl by default, and you can download free curl utilities for other OSes. By typing curl http://some-url, you can limit the request to print just the default response body (the HTML rendered by your browser). You can get much more information by typing curl -i http://some-url. This command returns the HTTP header, as shown in Listing 4. You see the header configuration, composed of key-value pairs that dictate the configuration of the individual request.


Listing 4. Invoking an HTTP request with curl

> curl -i http://localhost:3000/people/list
HTTP/1.1 200 OK 
Cache-Control: no-cache
Connection: Keep-Alive
Date: Tue, 27 Jun 2006 14:54:49 GMT
Content-Type: text/html; charset=UTF-8
Server: WEBrick/1.3.1 (Ruby/1.8.4/2005-12-24)
Content-Length: 854
Set-Cookie: _session_id=216912045de52786f032b22755c903dd; path=/

You'll frequently see the HTTP get, put, post, and delete commands. REST takes advantage of these commands to do classic CRUD, a common acronym for create, read, update, and delete. The HTTP commands map to CRUD like this:

  • Create: HTTP put
  • Read: HTTP get
  • Update: HTTP post
  • Delete: HTTP delete

Browsers use the HTTP header to satisfy many different kinds of requests with the same server-side code. Well-behaved applications provide enough information to process a document correctly. One of those pieces of information is called the HTTP Accept header. With just a little extra effort, your controller can employ some helpers that use the Accept header to determine how to respond to an incoming request. Then, the controller can render the appropriate response. Change the list method in the PeopleController to look like Listing 5:


Listing 5. Extending the list method to render XML

def list
 # wants is determined by the http Accept header in the request
 @people = Person.find_all
 respond_to do |wants|
   wants.html
   wants.xml { render :xml => @people.to_xml }
 end    
end

In Listing 5, you see a full REST-based Web service. The resulting code is a beautiful example of a tiny domain-specific language within Rails that extends Ruby to make a kind of switch statement. Here's how it works:

  1. The respond_to method accepts a single code block and passes one instance variable (labeled wants) into the code block.
  2. wants has a method for each possible type. The controller can specify a code block for each type the controller expects.
  3. A wants method executes the corresponding code block if the method name matches the type in the HTTP Accept header.
  4. If no code block is specified (such as wants.html), Rails performs the default action (in this case, rendering app/views/people/list.rhtml).

This strategy lets you share the same setup code across all expected clients. Should you need to add a JavaScript client expecting HTML to enable your application for Ajax, you could just add wants.js, as shown in Listing 6:


Listing 6. Rendering HTML for a JavaScript client
  
def list
   # wants is determined by the http Accept header in the request
   @people = Person.find_all
   respond_to do |wants|
      wants.html
      wants.js
      wants.xml { render :xml => @people.to_xml }
   end    
end

So you've seen how to add REST Web services to your read-only methods. The show method would be similar, as shown in Listing 7:


Listing 7. Implementing show

def show
   @person = Person.find(params[:id])
   respond_to do |wants|
       wants.html
       wants.xml { render :xml => @person.to_xml }
   end    
end

You may have noticed that you've seen only read-only services through REST. The reason is that preparing the application to handle posts and deletes is trivial. Deletes need no added support because the current code already uses the URL to specify the ID of the person to be deleted. Rails automatically translates incoming XML in post requests, so you do not need to build in any server-side support. In fact, the application works as-is for deletes, updates, and creates. You might tinker with the HTTP response that each method renders, but your client code is really after only the HTTP return code.

It's time to invoke the Web service.


Invoking the Web service

The strategy of using the existing HTTP protocol keeps your invocations simple. Listing 8 shows the Ruby version. Notice the HTTP Accept header. Remember, the controller determines the type of content to render based on that header.


Listing 8. Invoking the service from Ruby

require 'net/http'

Net::HTTP.start('localhost', 3000) do |http|
  response = http.get('/people/list', 'Accept' => 'text/xml')

  #Do something with the response.

  puts "Code: #{response.code}" 
  puts "Message: #{response.message}"
  puts "Body:\n #{response.body}"
end


The Web service invocation in Listing 8 invokes an HTTP get on http://localhost:3000/people/list and prints the response. Ruby has excellent libraries to deal with the resulting XML, but they are beyond this article's scope. You don't need to use Ruby to invoke this service. You need only a library for HTTP. Listing 9 shows a Java invocation of this service:


Listing 9. Invoking the service with Java code


package com.rapidred.ws;

import java.net.*;
import java.io.*;

public class SimpleGet {

    void get() {

        try {
            URL url = new URL("http://localhost:3000/people/list");
            URLConnection urlConnection = url.openConnection();
            urlConnection.setRequestProperty("accept", "text/xml");
            BufferedReader in = 
              new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String str;

            while ((str = in.readLine()) != null) {
                System.out.println(str);
            }

            in.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }

Like the Ruby counterpart, this code opens a URL connection, sets the Accept header to text/xml, issues the get, and prints the result. Many XML frameworks (see Resources) exist for Java code (as for Ruby), but I'll hardcode the XML in this one to keep the example simple.

Invoking a post is similar. Listing 10 shows a simple post:


Listing 10. Calling HTTP post with Java code

void post() {
try {
    String xmlText  = "<person> " +
            "<first-name>Maggie</first-name>" +
            "<last-name>Maggie</last-name>" +
            "<email>maggie@tate.com</email>" +
        "</person>";

    URL url = new URL("http://localhost:3000/people/create");
    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
    conn.setDoOutput(true);
    conn.setRequestMethod("POST");
    conn.setRequestProperty("Content-Type", "text/xml");
    OutputStreamWriter wr = new 
OutputStreamWriter(conn.getOutputStream());
    wr.write(xmlText);
    wr.flush();

    BufferedReader rd = new BufferedReader(new 
InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = rd.readLine()) != null) {
        System.out.println(line);
    }
    wr.close();
    rd.close();
    } catch (Exception e) {
        System.out.println("Error" + e);
    }
}


This HTTP post creates a new Person by simply invoking a post on http://localhost:3000/people/create and passing an XML document in the HTTP document body. (Normally, you'd use a Java XML library to construct the XML document. Again, I hardcoded the document to keep the example simple.) The Rails support automatically translates the incoming XML to a Ruby hash of Person attributes.


Wrapping up

In this article, you've seen that you can enable a controller for REST-based Web services with a trivial amount of code. Dynamically typed Internet languages such as Ruby make extensive use of REST instead of SOAP-based Web services. Some simple innovations, including the nifty responds_to syntax and automatic XML translation for incoming posts, make it easy to use the same controller from a Web service, remote JavaScript request, or HTML.

The Java language, too, has excellent support for REST. After all, a servlet is fundamentally a server-side REST-based Web service. You can use servlets on the Java side and Rails controllers on the Ruby side to knit together applications using the strengths of both platforms. That's the beauty of Web services. All you really need is the courage to break from the herd.


Resources

Learn

Get products and technologies

  • REXML and Xerces2: XML parsing frameworks for the Ruby and Java languages, respectively.

  • Ruby on Rails: Download the open source Ruby on Rails Web framework.

  • Ruby: Get Ruby from the project Web site.

Discuss

About the author

Bruce Tate

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby on Rails. His practice now offers a full range of Ruby and Rails education, consulting, and implementation offerings.

Comments



Trademarks

static.content.url=/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, SOA and Web services
ArticleID=151197
ArticleTitle=Crossing borders: REST on Rails
publish-date=08012006
author1-email=bruce.tate@j2life.com
author1-email-cc=bruce.tate@j2life.com