REST With Rails Part 1
Join the DZone community and get the full member experience.
Join For FreeIn this first part I will show you how to build RESTful services using Rails. REST is an architectural style modeled after the Web. Basically, it codifies the principles and methods behind Web servers that lead to the creation of the largest distributed system ever built. For some people "distributed" is about the plumbing – sending messages to remote servers – we're also thinking of the way large scale systems emerge from smaller services, built independently by different groups of people—distributed in design and in implementation.
[img_assist|nid=4838|title=|desc=|link=url|url=http://www.manning.com/mcanally/|align=right|width=139|height=174]When we think of REST, we think of following those very same principles. Modeling our services in terms of resources, making sure they are addressable as URLs, connecting them by linking from one resource to another, handling representations based on content type, performing stateless operations, and so forth. In the following sections we'll show that by example using Rails. You'll also quickly realize why we picked Rails for this task.
This article is based on chapter 5 from Ruby in Practice by Jeremy McAnally and Assaf Arkin. Courtesy of Manning Publications. All rights reserved.
RESTful Resources
Besides being the largest collection of useless information, personal opinions, and short video clips, the Web is also a large scale system built from resources. Each page is a resource identified by its URL. We use links to navigate from one resource to another, and forms to operate on these resources. Applying these same principles we can build Web services that are simple to use by both people and applications, and wire them together to create larger applications.
PROBLEM
We're building a task manager which our employees can use to manage their day to day tasks. We're also planning several applications and workflows that will create and act upon these tasks. How would we design our task manager as a Web service that both people and applications can use?
SOLUTION
Obviously one part of the solution is supporting programmable Web formats like XML and JSON, which we'll handle in the next section. Before we get to deal with that, we need to understand how to structure our resources, so we can consume them from Web browsers and client applications.
When we develop a Web service, our aim is to build the service once, and support any number of clients that want to connect to it. The more client applications that can reuse our service, the more we get out of the initial effort that goes into build that service. We're always on the lookout for those principles and practices that would make our service loosely coupled and ripe for reuse.
In this section we're going to do just that by applying REST principles to our task manager. We'll start by identifying the most important resources we need to provide. We have one representing the collection of tasks, which we'll make apparent by using the URL path /tasks. And since we also plan to operate on individual tasks, we'll give each task its individual resources, and we'll do so hierarchically by placing each task in a resource of the form /tasks/{id}.
We'll handle all of these through the TasksController, so the first thing we'll do is define the resource so Rails can map incoming request to the right controller. We do that in the config/routes.rb file:
ActionController::Routing::Routes.draw do |map|
# Tasks resources handled by TasksController
map.resources :tasks
end
Retrieving the list of all tasks in the collection is done by the index action:
class TasksController < ApplicationController
# GET on /tasks
# View: tasks/index.html.erb
def index
@tasks = Task.for_user(@user_id)
end
. . .
end
And for individual tasks, we're going to use the show action:
# GET on /tasks/{id}
# View: tasks/show.html.erb
def show
@task = Task.find(params[:id])
end
What else would we want to do with a task? We'll want to change (update) it, and we'll need to offer a way to delete it. We can do all three on the same resource. We can use HTTP GET to retrieve the task, PUT to update the task, and DELETE to discard it. So let's add two more actions that operate on a member task:
# PUT on /tasks/{id}
def update
@task = Task.find(params[:id])
@task.update_attributes! params[:task]
respond_to do |format|
format.html { redirect_to :action=>'index' }
format.xml { render :xml=>task } end
end
# DELETE on /tasks/{id}
def destroy
if task = Task.find_by_id(params[:id])
task.destroy
end
head :ok
end
We got a bit ahead of ourselves. Before we could do all these things on a task, we need some way to create it. Since we have a resource representing the collection of tasks, and each task is represented by its own resource, we're going to use HTTP POST to create a new task in the collection:
# POST on /tasks def create
task = Task.create!(params[:task])
respond_to do |format|
format.html { redirect_to :action=>'index' }
format.xml { render :xml=>@task, :status=>:created, :location=>url_for(:action=>'show', :id=>task.id) } end
end
We can now start to write applications that create, read, update and delete tasks. The beauty is that we've done it entirely using one resource to represent the collection, and one resource to represent each member, and used the HTTP methods POST (create), GET (read), PUT (update) and DELETE (delete). When it comes time to develop another service, say for managing users or orders, we can follow the same conventions, and we can take what we learned from one service and apply it to all other services.
We're not done, though. We want to expose this service to both people and applications. Our employees are going to use a Web browser, they're not going to send a POST or PUT request, but do that using forms. So we need two forms, one for creating a new task, and one for updating an existing task. We can place those inside the task list and individual task view, respectively. For larger forms, and our tasks will require a several fields taking up most of the page, we would want to offer those as separate pages linked from existing view pages, so we're going to offer two additional resources.
From the tasks list we're going to link to a separate resource representing a form for creating new tasks, and we'll keep designing our resources hierarchically, assigning it the URL path /tasks/new. Likewise, we'll associate each individual task with a URL for viewing it and a URL for editing it:
# GET on /tasks/new
# View: tasks/new.html.erb
def new
@task = Task.new
end
# GET on /tasks/{id}/edit
# View: tasks/edit.html.erb
def edit
@task = Task.find(params[:id])
end
Now it's becoming more clear why we choose to lay out the resources hierarchically. If you like tinkering with the browser's address bar, try this: open up the edit form for a given task, say /tasks/123/edit, and change the URL to go up one level to the task view at /tasks/123, and up another level to the tasks list at /tasks. Besides being a nice browser trick, this setup helps developers understand how all the resources relate to each other. This is one case where picking intuitive URLs is worth a 1000 words of documentation.
So let's pause and review what we have so far:
- GET request to /tasks return the list of all tasks
- POST request to /tasks creates a new task and redirects back to the tasks list
- GET request to /tasks/new returns a form that we can use to create a new task, it will POST to /tasks
- GET request on /tasks/{id} returns a single task
- PUT request on /tasks/{id} updates that task
- DELETE request on /tasks/{id} deletes that task
- GET request on /tasks/{id}/edit returns a form that we can use to update an existing task, it will PUT these changes to /tasks/{id}
We got here not by accident. We intentionally chose these resources so all we need to do is keep track of one reference (URL) to the tasks list, and one reference to each individual task. Helping us was the fact that we can use all four HTTP methods which already define the semantics of operations we can do against these resources. Notice that, while adding more actions to our controllers, we made no change to our routing configuration. These conventions are a matter of practical sense, and Rails follows them as well, and so our one-line definition of the resource captures all that logic, all we had to do was fill in the actions. Next, we're going to add a couple of actions that are specific to our task manager, and extend our resource definition to cover those.
The first resource we're going to add is for viewing the collection of completed tasks, and we can follow the same rules to add resources for viewing pending tasks, tasks scheduled to complete today, high priority tasks, and so forth. We're going to place it at the URL path /tasks/completed. The second resource we're going to add will make it easier to change task priority. Right now making a change to the task requires updating the task resource. We want to develop a simple AJAX control that shows five colored numbers, and clicking on one of these numbers sets the task priority. We'll make it easy by providing a resource to represent the task priority, so we can write an onClick event handler that updates the priority by sending it a new priority number. We consider it part of the task resource, so we'll associate it with the URL path /tasks/{id}/priority. So let's add these two resources together and create the routes shown in listing 1.
ActionController::Routing::Routes.draw do |map|
# Tasks resources handled by TasksController
map.resources :tasks, :collection => { :completed=>:get }, :member => { :priority=>:put } end
And now let's go and add the controller actions:
# GET on /tasks/completed
# View: tasks/completed.html.erb
def completed
@tasks = Task.completed_for_user(@user_id)
end
# PUT on /tasks/{id}/priority
def priority
@task = Task.find(params[:id])
@task.update_attributes! :priority=>request.body.to_i
head :ok
end
Will it work? We certainly hope so, but we won't know until we check. Rails resource definitions are easy to work with, but we still occasionally make mistakes and create something different from what we intend. So let's investigate our route definitions using the routes task:
$ rake routes
The output would look something like listing 2.
completed_tasks GET | /tasks/completed | {:action=>"completed"} |
tasks GET | /tasks | {:action=>"index"} |
POST | /tasks | {:action=>"create"} |
new_task GET | /tasks/new | {:action=>"new"} |
completion_task PUT | /tasks/:id/completion | {:action=>"completion"} |
edit_task GET | /tasks/:id/edit | {:action=>"edit"} |
task GET | /tasks/:id | {:action=>"show"} |
PUT | /tasks/:id | {:action=>"update"} |
DELETE | /tasks/:id | {:action=>"destroy"} |
The actual output is more verbose, we trimmed it to fit the narrow pages of this book, by ignoring the controller name (no surprise, it's always "tasks") and removed the formatting routes that we'll cover in the next section. We kept the rest. You can see how each HTTP method (second column) and URL template (third column) map to the right controller action (right-most column). A quick peek tells us all we need to know. The left-most column deserves a bit more explanation. Rails creates several friendly looking routing methods that we can use in stead of the catch-all url_for. For example, since our tasks list needs a link to the URL for the task creation form, we can write this:
<%= link_to "Create new task", url_for(:controller=>'tasks', :action=>'new') %>
Or using the named route method, shorten it to:
<%= link_to "Create new task", new_task_url %>
We can place next to each task appearing in the tasks list, pointing to that task's view:
<%= link_to task.title, task_url(task) %>
Or a link for the task editing form:
<%= link_to "Edit this task", edit_task_url(task) %>
We're done, so let's have a look at what our controller looks like with all the actions brought together in one file. As we write it up, we're going to make a couple of minor tweaks. First, we'll use named routes instead of url_for. Second, we'll add a filter to load the task into the controller, for the benefit of actions operating on individual tasks. Listing 3 shows what the resulting controller looks like.
class TasksController < ApplicationController
before_filter :set_task, :only=>[:show, :edit, :update, :complete]
def index
@tasks = Task.for_user(@user_id)
end
def completed
@tasks = Task.completed_for_user(@user_id)
end
def new
@task = Task.new
end
def create
task = Task.create!(params[:task])
respond_to do |format|
format.html { redirect_to tasks_url }
format.xml { render :xml=>task, :status=>:created, :location=>task_url(task) } | end
end
def show
end
def edit
end
def update
@task.update_attributes! params[:task]
respond_to do |format|
format.html { redirect_to tasks_url }
format.xml { render :xml=>@task } end
end
def priority
@task.update_attributes! :priority=>request.body.to_i
head :ok
end
def destroy
if task = Task.find_by_id(params[:id]) task.destroy end
head :ok
end
private
def set_task
@task = Task.find(params[:id])
end
end
DISCUSSION
We showed you how to build a simple RESTful Web service using Rails. There’s a few more things worth noting about this example and how we used Rails to apply the principles of REST. One of the core principles of REST is the uniform interface. HTTP provides several methods you can use on each resource, the four we're showing here are POST (create), GET (read), PUT (update) and DELETE (delete). They have clear semantics and everyone understands them the same way. Clients know what GET does and how it differs from DELETE, servers operate differently on POST and PUT, caches know they can cache the response to a GET but must invalidate it on DELETE, and so forth. You can also use that to build more reliable applications, for example, PUT and DELETE are idempotent methods, so if you fail while making a request, you can simply repeat it a second time. The uniform interface saves us from having to reinvent and document these semantics for each and every application, it helps that we can always do the same thing the same way.
Unfortunately, while we get this variety for the programmable Web, Web browsers have not caught up yet, and some cannot properly handle PUT and DELETE. A common workaround is to use POST to simulate PUT and DELETE by sending the real HTTP method in the _method parameter. Rails understands this convention, and so do many AJAX libraries like Prototype.js and jQuery, so you can safely use these with Rails to keep your resources RESTful. You will notice in our example that, when updating an existing resource (the task) we respond to the PUT request with the default status code 200 (OK) and an XML representation of the updated resource. On the other hand, when creating a new resource, we respond to the POST request with the status code 201 (Created), an XML representation of the new resource, and the Location header. The later tells the client application that we just created a new resource, and where to find that resource, for example, to update it later on. In both cases, we return a document that may be different from the one we received, for example adding fields like id, version and updated_at. Either way, we're using the full semantics of the HTTP protocol to distinguish between creating a new resource and updating an existing one.
People work different form applications, so when responding to a Web browser, we worry about the user experience. The way browsers work, if we simply responded to a POST request with a render, and the user then decides to refresh the page, the browser will make another POST request, the double-submit problem. We don't want that to happen, so we redirect instead. We also don't need to send back a representation of the resource, or its location, instead we decide to take the user back to the tasks lists.
You may be wondering, what happens if someone makes a request to /tasks/456, but there is no such task? Clearly this should return a 404 (Not Found) response, yet we show no such thing in our example. We let Rails figure it out.
When we call Task.find, it throws an ActiveRecord::RecordNotFound exception if it can't find a task with that identifier. Rails catches this exception and maps it to the 404 (Not Found) status code. The default behavior is to send back a static page that you can find (and customize to your application) in public/404.html.
Likewise, if we tried to create or update a task by sending a field it doesn't understand, for example an XML document with the element <address> (our tasks don't have an address field), Rails will throw an ActiveRecord::RecordInvalid or ActiveRecord::RecordNotSaved exception. It will then catch this exception and map it into a 422 (Unprocessable Entity) status code.
You can add your own logic for catching and dealing with these exceptions, and also introduce your own exception and handling logic. Have a look at ActionController::Rescue, in particular the rescue_from method. One way in which Rails simplifies deployment is by taking care of all these details and applying default behavior, so you don’t have to worry about it unless you want to change the way it behaves. Another example is how Rails deals with unsupported content types, by returning 406 (Not Acceptable), which we’ll put into action in the next section. Speaking of finder methods, one common mistakes Web developers do is storing the copy of an object in the session, for example:
Task.find_by_user(session[:user])
What's wrong with this code? Updating the user's record in the database, or even deleting it, will not update the session, and the session will keep using stale data. It is much better to store the record identifier, which doesn't change, and access the record as necessary. The common alternative looks like this:
Task.find_by_user_id(session[:user_id])
This code works better as long as you're using sessions. When developing applications that use a Web service, it's much easier to work with HTTP Basic Authentication, as we've shown in the previous sections. It's easier to use than going through a custom login form and then carrying the session cookie around.
Fortunately, it's a trivial matter to write controllers that support both means of authentication. Simply, add a filter than can use HTTP Basic Authentication or the session to identify the user and store their identifier in the @user_id instance variable. We recommend doing that in ApplicationController, which is why we're not showing this filter in our example.
We talked about the ease of mapping resources for CRUD operations (created, read, update, delete). Resource mapping is another area where we encourage you to explore more. You can take hierarchical resources one step further and create nested resources, for example, /books/598/chapters/5. You can use the to_param method to create more friendly URLs, for example, /books/598-ruby-in-practice/chapters/5-web-services. Also, have a look at some of the form helper methods that will generate the right form from an ActiveRecord object, using the most suitable resource URL. This combination will not only make it easier to develop Web applications, but also help you do the right thing from the start.
When building RESTful Web services another thing we have to deal with are multiple content types. We briefly touched upon this, using HTML for end-users and XML for applications, and in part 2 we will explore it further, adding support for JSON and Atom.
Opinions expressed by DZone contributors are their own.
Trending
-
Is Podman a Drop-in Replacement for Docker?
-
Competing Consumers With Spring Boot and Hazelcast
-
Microservices With Apache Camel and Quarkus
-
RBAC With API Gateway and Open Policy Agent (OPA)
Comments