Restful Development
Restful Development
License
This work is licensed under the Creative Commons Attribution-No
Derivative Works 2.0 Germany License. To view a copy of this license,
visit http://creativecommons.org/licenses/by-nd/2.0/de/ or send a
letter to Creative Commons, 543 Howard Street, 5th Floor, San Fran-
cisco, California, 94105, USA.
1 RESTful Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 What is REST? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Why REST? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 What’s New? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Preparations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4.1 Rails 1.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Resource Scaffolding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.6 The Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.7 The Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.7.1 REST URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.7.2 REST Actions Use respond to . . . . . . . . . . . . . . . . . . . . 7
1.7.3 Accept Field of the HTTP Header . . . . . . . . . . . . . . . . . 8
1.7.4 Format Specification Via the Request URL . . . . . . . . . . . . 9
1.8 REST URLs and Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.8.1 New and Edit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.8.2 Path Methods in Forms: Create and Update . . . . . . . . . . . 12
1.8.3 Destroy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.9 URL Methods in the Controller . . . . . . . . . . . . . . . . . . . . . . . 13
1.10 REST Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.10.1 Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.10.2 Customizing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.11 Nested Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.11.1 Adapting the Controllers . . . . . . . . . . . . . . . . . . . . . . 18
1.11.2 New Parameters for Path and URL Helpers . . . . . . . . . . . . 18
1.11.3 Adding New Iterations . . . . . . . . . . . . . . . . . . . . . . . 20
1.11.4 Editing Existing Iterations . . . . . . . . . . . . . . . . . . . . . . 22
1.12 Defining Your Own Actions . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.12.1 Are We Still DRY? . . . . . . . . . . . . . . . . . . . . . . . . . . 25
IV Contents
Bibliography . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Chapter 1
RESTful Rails
HTTP can do more then GET and POST, a fact that is almost forgotten by many web
developers these days. But if you consider that browsers only support GET and
POST requests, maybe this shouldn’t be a surprise.
GET and POST are two of the types of HTTP requests that are often transmitted from
clients to servers. The HTTP protocol also defines PUT and DELETE methods which
are used to tell the server to create or delete a web resource.
This tutorial’s aim is to broaden developers’ horizons about the HTTP methods PUT
and DELETE. One use of PUT and DELETE together with GET and POST that has
become popular recently is referred to by the common term ”REST”. One of the
highlighted new features of Rails 1.2 is its support of REST.
The tutorial starts with a short introduction into the concepts and the background
of REST. Building on this, the reasons for developing RESTful Rails applications are
explained. Using scaffolding, a detailed development of a REST controller and its
model shows us the technical tools that help us with RESTful development. With
this technical base in mind, the next chapter shows the general functionality and
customization of so-called REST Routing, on which REST functionality is heavily
dependent. The chapter Nested Resources introduces the reader to the advanced
school of REST development and explains how resources can be nested in a parent-
child relationship without violating the concept of REST URLs. The tutorial ends
with chapters about REST and AJAX, testing of RESTful applications and an intro-
duction to ActiveResource–the client-side part of REST.
Before we start, one last word: this tutorial assumes that you have at least a basic
knowledge of Rails development. If this is not the case, please consider working
through one of the many Rails tutorials available on the Internet (e.g., [3], [4], [5]), or
read one of many books on the subject (e.g., [1] and [2]).
2 1 RESTful Rails
Web
<<Project>>
Wunderloop
http://ontrack.b-simple.de/projects/1
<<Project>>
http://ontrack.b-simple/projects/2 Webcert
http://ontrack.b-simple.de/projects/3
<<Project>>
Bellybutton
All three resources in figure 1.1 are addressed by URLs that are almost identical,
followed by the id of the resource. Note that the URL doesn’t show what should
happen with the resource.
In the context of a Rails application, a resource is a combination of a dedicated con-
troller and a model. So from a technical standpoint, the project resources from figure
1.1 are instances of the ActiveRecord class Project in combination with a ProjectsCon-
troller which is responsible for the manipulation of the instances.
1 If
we want to make a distinction between REST- and non-REST-based Rails applications, we use the
word traditional. Traditional does not mean old or even bad, it’s only used to make a reference to an
equivalent non-REST concept. This comparison should help to better explain the new technology.
1.2 Why REST? 3
Once you have an understanding of REST and begin using its techniques, RESTful
application design becomes second nature.
1.4 Preparations
We’re going to explain the new REST-specific features of Rails within the context of
the example application in our book Rapid Web Development mit Ruby on Rails [1],
Ontrack, a project management application. We will not develop the full application
here, but will use the same terminology to create a technical environment for REST
concepts.
Let’s start with the generation of our Rails project:
> rails ontrack
Alternative installation methods and information about newer versions of Rails can
be found on the Rails home page http://www.rubyonrails.org.
exists test/unit/
create app/views/projects/index.rhtml
create app/views/projects/show.rhtml
create app/views/projects/new.rhtml
create app/views/projects/edit.rhtml
create app/views/layouts/projects.rhtml
create public/stylesheets/scaffold.css
create app/models/project.rb
create app/controllers/projects_controller.rb
create test/functional/projects_controller_test.rb
create app/helpers/projects_helper.rb
create test/unit/project_test.rb
create test/fixtures/projects.yml
create db/migrate
create db/migrate/001_create_projects.rb
route map.resources :projects
In addition to the model, controller, and views, the generator also creates a fully-
functional migration script and a new mapping entry map.resources :projects in con-
fig/routes.rb. It is this last entry that is responsible for the RESTful character of our
new controller. We won’t go too deeply into it just yet–instead, we’ll go through all
the generated parts, step by step.
So in the case of the model, there is nothing new to learn. But don’t forget to generate
the database table:
> rake db:migrate
3 Additionally,the controller consists of the action index, to display a list of all resources of this type, the
new action, for opening the new form, and the edit action, for opening the editing form.
6 1 RESTful Rails
# GET /projects/1
# GET /projects/1.xml
def show...
# GET /projects/new
def new...
# GET /projects/1;edit
def edit...
# POST /projects
# POST /projects.xml
def create...
# PUT /projects/1
# PUT /projects/1.xml
def update...
end
# DELETE /projects/1
# DELETE /projects/1.xml
def destroy...
end
If you look at the generated controller you will not find very much new here: there
are actions for creating, retrieving, updating and deleting projects. Both controller
and actions appear normal at a first glance, but they all have a generated comment
showing the relevant request URL including its HTTP verb. These are the REST
URLs, and we will take a closer look at them in the next section.
With the loss of the action name, it is no longer obvious what should happen to
the indicated resource. Should the above shown URL list or delete the resource?
The answer to this question comes from the HTTP method4 which is used when
4 In this tutorial, we will use the term HTTP verb to describe the HTTP method because it better expresses
that the request will result in an action.
1.7 The Controller 7
requesting the URL. The following table lists the four HTTP verbs along with their
REST URLs and shows how they correspond to controller actions and traditional
Rails URLs:
The HTTP verb determines which action will be executed. URLs become DRY and
address resources instead of actions. The REST URLs are identical for all operations
except for POST because when creating a new project an id does not yet exist.
Remark: Entering the URL http://localhost:3000/projects/1 in the browser always
calls show because when a browser fetches a URL, it uses the GET verb. Since
browsers only use GET and POST HTTP verbs, Rails must perform a bit of sleight-
of-hand to enable destroy and update actions. Rails provides special helpers for the
creation of links to delete and update resources: the HTTP DELETE verb is trans-
mitted to the server in a hidden field inside of a POST request (see section 1.8.3) for
delete actions, and the PUT verb is similarly sent to update existing resources (see
section 1.8.2).
end
end
The method respond to gets a block with format-specific instructions. In the ex-
ample, the block handles two formats: HTML and XML. Depending on the client’s
requested format, the corresponding instructions are executed. If HTML is the for-
mat requested, nothing is specified, which causes the delivery of the default view,
show.rhtml. If the requested format is XML, the requested resource will be converted
into XML and delivered to the client.
The requested format of the response is indicated in one of two ways: it’s either
put into the Accept field of the HTTP header of the request or it’s appended to the
request URL.
The Rails dispatcher routes this request to the show action. Because of our XML pref-
erence in the HTTP Accept field, the method respond to executes the format.xml
block and the requested resource is converted into XML and delivered as the re-
sponse.
curl is not only good for testing of different response formats–it is also good for
sending HTTP verbs that are normally not supported by web browsers. For example,
the following request deletes the project resource with id 1:
> curl -X DELETE http://localhost:3000/projects/1
=>
<html><body>You are being
<a href="http://localhost:3000/projects">redirected</a>.
</body></html>
This time the request uses the HTTP DELETE verb. The Rails dispatcher evaluates
the HTTP verb and routes the request to the ProjectsController.destroy method. Note
that the URL is identical to the URL used in the last curl request. The only difference
is the HTTP verb that was used.
Attention Mac users: This request is easier to observe in Firefox than in Safari be-
cause Safari simply ignores the delivered XML. Firefox formats the XML nicely (see
figure 1.3).
So far, we have learned how REST controllers work and what the appropriate request
URLs look like. In the following two sections, we will see how to use and generate
these URLs in views and controllers.
10 1 RESTful Rails
What immediately springs to mind is that this traditional usage of link to doesn’t
work very well with our new REST philosophy–REST specifies that URLs identify
the resource and the action is determined by the HTTP verb of the request. What
needs to happen is for links and buttons to deliver the appropriate HTTP verb to-
gether with the URL.
Rails provides the solution: it still uses link to to generate the link, but the hash
is replaced with a call to a Path method. Path methods create link destinations that
link to puts into the href attribute of the generated link. As a first example, we create
a link to the show action of the ProjectsController. Instead of specifying a controller,
action and project id in a hash, project path() is used:
link_to "Show", project_path(project)
=>
<a href="/projects/1">Show</a>
requests (show, create) are transmitted by default with the right HTTP verb (here,
GET and POST). Others (update, delete) need to be treated in a special way (using
hidden fields) because, as was already mentioned, browsers don’t ever use PUT and
DELETE verbs. You will read more about this special treatment and its implementa-
tion in section 1.8.3 and section 1.8.2.
A closer look at the table shows that four HTTP verbs are not enough to map all of
the CRUD actions. The first two methods, projects path and project path(1), work
well with GET and can be routed directly to their appropriate actions. However,
things don’t look as bright for new project path and edit project path.
Is this a crack in the REST philosophy? Maybe at a first glance. But if you look
closer it becomes clear that new is not a REST/CRUD action–it doesn’t modify any-
thing in the application’s data–it is more of a preparatory action for the creation of a
new resource. The real CRUD action create is first executed when the new form is
eventually submitted. The resource id is not present in the URL because there is no
resource yet.
It’s a similar story for the method edit project path –it refers to a concrete resource,
but it’s not a CRUD action. edit project path is used to prepare the real CRUD ac-
tion call when update is requested. The difference between edit project path and
new project path is that edit project path needs the id of the project to be manip-
ulated. Following the REST convention, the id comes after the controller: /pro-
jects/1. However, if this path were submitted to the server with GET, the call would
be routed to the show action. To distinguish the edit action, edit project path sim-
ply extends the generated href attribute in a special way. This is how the generated
HTML link finally looks:
link_to "Edit", edit_project_path(project)
12 1 RESTful Rails
=>
<a href="/projects/1;edit">Edit</a>
It’s okay for new and edit to include the action in their URL because neither are real
REST/CRUD actions. The same principle is also used for developing other actions
that use names other than the standard CRUD names. We will have a look at this in
section 1.12.
In a typical REST application, the :url hash is set to the return value of a call to a Path
method:
Rails generates a hidden method field that contains the appropriate HTTP verb put.
The dispatcher looks at this field and routes the request to the update action.
1.9 URL Methods in the Controller 13
1.8.3 Destroy
Note that the method used for both showing and deleting a project is project path :
link_to "Show", project_path(project)
link_to "Destroy", project_path(project), :method => :delete
The only difference is that the destroy link additionally uses the parameter :method
to name the HTTP method to use (:delete, in this case). Because the web-browser
doesn’t support the DELETE verb, Rails generates a JavaScript fragment that gets
executed when clicking on the link:
link_to "Destroy", project_path(project), :method => :delete
=>
<a href="/projects/1"
onclick="var f = document.createElement(’form’);
f.style.display = ’none’; this.parentNode.appendChild(f);
f.method = ’POST’; f.action = this.href;
var m = document.createElement(’input’);
m.setAttribute(’type’, ’hidden’);
m.setAttribute(’name’, ’_method’);
m.setAttribute(’value’, ’delete’); f.appendChild(m);f.submit();
return false;">Destroy</a>
This script creates a form on the fly and ensures that the DELETE HTTP verb gets
transmitted to the server in the hidden field method. Again, the Rails dispatcher
analyzes the content of this field and sees that the request should be routed onto the
action destroy.
projects_url
=>
"http://localhost:3000/projects"
14 1 RESTful Rails
In the controllers of a REST application, URL methods are used everywhere where
the redirect to method gets traditionally handed over a controller/action/parameter
hash. So the following:
redirect_to :controller => "projects", :action => "show",
:id => @project.id
You can find an example for this in the destroy action, where projects url is used
without any parameters to redirect to a list of all projects after a project has been
deleted:
respond_to do |format|
format.html { redirect_to projects_url }
format.xml { head :ok }
end
end
The entry was generated by the scaffold resource generator. It creates the named
routes that are needed for requesting the REST actions of the ProjectsController.
In addition, resources generates the Path and URL methods for the Project resource
that we’ve been experimenting with in this chapter:
map.resources :projects
=>
Route Generated Helper
-----------------------------------------------------------
projects projects_url, projects_path
project project_url(id), project_path(id)
new_project new_project_url, new_project_path
edit_project edit_project_url(id), edit_project_path(id)
1.10 REST Routing 15
1.10.1 Conventions
A necessary consequence of development using REST routes is to comply with the
naming conventions for the controller methods that handle the CRUD actions. The
following link to call, the resulting HTML, and the Rails dispatcher’s behavior illus-
trate this:
link_to "Show", project_path(project)
=>
<a href="/projects/1">Show</a>
Neither the link to call nor the generated HTML include the name of the action to
call. The Rails dispatcher knows that the route /projects/:id must be routed onto
the show action of the ProjectsController if the request was sent via a GET verb. The
controller must have an action with the name show. The same convention is true for
the actions index, update, create, destroy, new and edit –each REST controller must
implement these methods.
1.10.2 Customizing
REST routes can be adapted to application-specific requirements with the help of the
following options:
The following routing entry creates routes for a new resource, Sprint. Sprint is a
synonym for an iteration and maps onto the ActiveRecord model Iteration to be
introduced in the next section:
map.resources :sprints,
:controller => "ontrack",
:path_prefix => "/ontrack/:project_id",
:name_prefix => "ontrack_"
The option path prefix is used for the URL format. Each URL starts with /ontrack
followed by a project id. The responsible controller should be OntrackController.
Therefore the URL
http://localhost:3000/ontrack/1/sprints
gets routed according to the given routing rules to the index action of the Ontrack-
Controller. Another example is the URL
http://localhost:3000/ontrack/1/sprints/1
While path prefix defines the format of URLs and paths, name prefix makes gener-
ated helper-methods start with ontrack . For example:
ontrack_sprints_path(1)
=>
/ontrack/1/sprints
or
ontrack_edit_sprint_path(1, 1)
=>
/ontrack/1/sprints/1;edit
In addition to creating model, controller and views, the generator also creates a new
routing entry in config/routes.rb :
map.resources :iterations
As with the similar line for projects, this statement generates new routes and helpers
for the manipulation of the Iteration resource. However, iterations only make sense
in the context of a concrete project–this is not taken into consideration by the created
routes and helpers. For example, the helper new iteration path creates the path /it-
erations/new, which contains no information about the project that the new iteration
belongs to.
The point of nested resources is essentially the realization that a child resource (here,
an Iteration) doesn’t–and can’t–exist outside of the context of the parent resource (in
this case, a Project) to which it belongs. REST Rails tries to reflect this in the usage
of URLs and the controller of the child resource. To get this to work, you need to
replace the generated resource entry in config/routes.rb :
map.resources :iterations
with:
map.resources :projects do |projects|
projects.resources :iterations
end
This entry causes Iteration to be a nested resource and generates appropriate routes
that allow you to manipulate iterations only in the context of a project. The generated
routes have the following format:
/project/:project_id/iterations
/project/:project_id/iterations/:id
results in the index action of the IterationsController being executed, getting the id
of the project via the request parameter :project id. Note especially how the URL
clearly indicates the underlying ActiveRecord association:
/projects/1/iterations <=> Project.find(1).iterations
Nested REST URLs are still clean REST URLs, meaning they address resources and
not actions. The fact that a resource is a nested resource is indicated by two REST
URLs appearing one after another in one URL. A request for the show action should
make this clear:
http://localhost:3000/projects/1/iterations/1
18 1 RESTful Rails
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @iterations.to_xml }
end
end
We need to rewrite the action so that only the iterations of the chosen project are
loaded:
All the actions of IterationsController will only work properly with a /pro-
jects/:project id prefix–they require that the parent project that defines the context of
the iteration actions be present. Since the scaffold resource generator doesn’t create
controller code that handles nested resources, not only does index need to be rewrit-
ten, but the actions create (see section 1.11.3) and update (see section 1.11.4) need to
be adjusted, too.
=>
<a href="/projects/1/iterations">Iterations</a>
For a better understanding, let’s look at the link where it actually appears, in the
ProjectsControllers index view:
A consequence of the changed parameter list is that not only are some actions in the
controller broken, but also many scaffold views for the iterations. For example, the
index view contains a table with all iterations, and each iteration has three links:
All links start out with the id of the respective iteration as the first and only param-
eter. This doesn’t work any longer because the first parameter of an iteration helper
should be the project id. The needed change looks like this:
An alternate way to correct the parameter list of the nested resource helpers is to
pass the required ids in a hash:
iteration_path(:project_id => iteration.project, :id => iteration)
This increases the readability of code when it’s not immediately clear what object
type the iteration relates to.
For the path method we use new iteration path which generates for the project with
id 1 the following HTML:
link_to "New Iteration", new_iteration_path(project)
=>
<a href="/projects/1/iterations/new">New iteration</a>
The link routes to the new action of the IterationsController. The action receives the
value 1 via the request parameter project id, which is the id of the current project.
1.11 Nested Resources 21
The project id is thereby available in the rendered new view of the IterationsCon-
troller and can be used there by the helper iterations path, which is responsible for
the generation of the new iteration form. The generated form contains a nested route
in the action attribute, which contains the id of the project in which a new iteration
should be created:
The usage of params[:project id] in iterations path is optional because Rails auto-
matically sets the request’s project id parameter as the generated action attribute.
This means that
form_for(:iteration, :url => iterations_path)
In line 3, the project is assigned explicitly. We have also extended the helper itera-
tion url with the project id in lines 8 and 11.
To make adding of new iterations really work, you need to extend the Edit and Back
links in the show view of the IterationsController to include the project id:
This view is rendered after the creation of a new iteration and would otherwise
throw an exception if we didn’t pass in the project id.
A similar change needs to be made in the update action that is called from the form–
the method iteration url in line 7 is passed only the iteration id after a successful
update:
After all these changes have been made, the create and update views and their ac-
tions should finally be working. Iterations can now be created and edited. But to
be absolutely sure, look closely at the IterationsController and its respective views.
Check all path and URL helpers, looking for any that don’t aren’t receiving a project
id and change them as we changed the create and update views.
def self.down
remove_column :projects, :closed
end
end
rake db:migrate
Because close is not a typical CRUD action, Rails does not know which HTTP verb
it should use. Close is a specialized action of the project, a kind of an update to
the project record, so according to REST, it should be sent using POST. We define
the route and the corresponding helper in the routing file config/routes.rb with the
help of the member hash in the resources call for projects. The hash consists of
action-method pairs and specifies which action should or is allowed to be called
with which HTTP verb5 .
Allowed values are :get, :put, :post, :delete and :any. If an action is marked with :any,
it is allowed to call the action with any HTTP verb. In our example, close should be
requested via POST, so we have to change the resources entry as shown below:
map.resources :projects, :member => { :close => :post }
After adding this entry, we can use the new helper close project path when creating
the Close link we talked about earlier:
<td><%= link_to "Close", close_project_path(project) %></td>
The route exists but the new resources entry allows only requests via HTTP POST.
Other HTTP methods (like GET, used in the link above), are denied by Rails. What
we need is something similar to the destroy link: a helper that generates a form that
is sent via POST to the server. Luckily, Rails has such a helper–button to does exactly
what we need:
<td><%= button_to "Close", close_project_path(project) %></td>
=>
<td>
<form method="post" action="/projects/1;close" class="button-to">
<div><input type="submit" value="Close" /></div>
</form>
</td>
5 Rails
covers the routes with HTTP restrictions, resulting in RoutingError Exceptions if an Action is re-
quested with the wrong HTTP verb.
1.13 Defining your own Formats 25
else
flash[:notice] = "Error while closing project."
format.html { redirect_to projects_path }
format.xml { head 500 }
end
end
end
Besides :member, the keys :collection and :new can be specified in the resources call.
:collection is required when the action is performed on a collection of resources of a
particular type, rather than a single resource of the type. An example is requesting a
project list as an RSS feed:
map.resources :projects, :collection => { :rss => :get }
--> GET /projects;rss (maps onto the #rss action)
The hash key :new is used for actions that work on new resources that are not yet
saved:
map.resources :projects, :new => { :validate => :post }
--> POST /projects/new;validate (maps onto the #validate action)
wants.yaml
end
As an extension to this you can register your own formats as MIME types. Let’s
say you have developed a PIM application and you want to deliver the entered ad-
dresses via the vcard format6 . To do that, you first must register the new format in
the configuration file, config/environment.rb, like this:
Mime::Type.register "application/vcard", :vcard
Now we can extend the show action of the AddressesController so that it can deliver
addresses in the vcard format if the client asks for it.
def show
@address = Address.find(params[:id])
respond_to do |format|
format.vcard { render :xml => @address.to_vcard }
...
end
end
The method to vcard is not a standard ActiveRecord method and must be imple-
mented using the vcard specification (RFC2426). If implemented correctly, the fol-
lowing URL should result in an address, delivered in standard vcard XML syntax:
http://localhost:3000/addresses/1.vcard
One note: don’t forget to include the needed JavaScript libraries if you don’t want
to waste a quarter of an hour figuring out why the link is not working (as I did).
One way to achieve this is to call the javascript include tag helper in the layout file
projects.rhtml of the ProjectsController:
6 http://microformats.org/wiki/hcard
1.14 RESTful AJAX 27
A click on the link gets routed to the destroy action of the ProjectsController. From
a business logic point of view, the method already does everything right: it deletes
the chosen project. What’s missing is an additional entry in the respond to block
for delivering the client the newly requested format, in this case JavaScript. The
following piece of code shows the already-updated destroy action:
respond_to do |format|
format.html { redirect_to projects_url }
format.js # default template destroy.rjs
format.xml { head :ok }
end
end
The only change to the old version is the additional format.js entry in the respond to
block. Because the new entry has no further block of code to execute, Rails acts in
the standard manner and delivers an RJS template with the name destroy.rjs. It looks
like this:
The template deletes the element with the id project ID from the web browsers DOM
tree. To make this work in the index view of ProjectsController you have to add a
unique id to the table rows:
1.15 Testing
No matter how exciting RESTful development with Rails is, testing shouldn’t be
forgotten! That we have already developed too much without running our unit tests
at least once becomes clear when we finally run the tests with rake 7 :
> rake
...
Started
EEEEEEE.......
The good news: all unit tests and the ProjectsControllerTest functional test are still
running. The bad news: all seven IterationsControllerTest functional tests are bro-
ken.
If all tests of a single test case throw errors, it’s a clear sign that something funda-
mental is wrong. In our case the error is obvious: the test case was generated by the
scaffold generator for iterations without a parent project. We extended the iterations
to make them belong to a project when we added all the needed functionality in
IterationsController and now all actions there are expecting the additional request
parameter, :project id. To fix this, extend the request hash of all test methods with
the parameter project id. As an example, we take the test test should get edit :
After all these changes only two tests should still fail: test should create iteration
and test should update iteration. In both cases, the reason is a wrong as-
sert redirected to assertion:
assert_redirected_to iteration_path(assigns(:iteration))
It’s obvious what’s going on here: we have changed all redirects in the IterationsCon-
troller so that the first parameter is the project id. The assertion checks only if there
is an iterations id in the redirect call. In this case, the controller is right and we have
to adopt the test:
assert_redirected_to iteration_path(projects(:one),
assigns(:iteration))
By the way, the use of path methods in redirect assertions is the only difference be-
tween REST functional tests and non-REST functional tests.
7 Attention: Don’t forget to create the test database ontrack test if you haven’t already!
1.16 RESTful Clients: ActiveResource 29
ActiveResource abstracts client-side web resources as classes that inherit from Ac-
tiveResource::Base. As an example, we use the existing server-side resource Project
that we model on the client side as follows:
require "activeresource/lib/active_resource"
The ActiveResource library is explicitly imported. Additionally, the URL of the ser-
vice gets specified in the class variable site. The class Project abstracts the client-side
part of the web service so well that the programmer gets the impression he is work-
ing with a normal ActiveRecord class. For example, there is a find method that
requests a resource with the given id from the server:
wunderloop = Project.find 1
puts wunderloop.name
The server delivers the answer in XML. From the XML, the client generates an Ac-
tiveResource object wunderloop that offers, like an ActiveRecord model, getter and
setter methods for all of its attributes. But how does it work with updates?
wunderloop.name = "Wunderloop Connect"
wunderloop.save
The call to save converts the resource into XML and sends it via PUT to the server:
PUT /projects/1.xml
Take your web browser and reload the list of projects. The changed project should
have a new name.
Creating new resources via ActiveResource is as easy as requesting and updating
resources:
8 http://subversion.tigris.org/
30 1 RESTful Rails
The new project is transmitted to the sever in XML via POST and gets saved into the
database:
POST /projects.xml
Reloading the project list view in the browser shows the newly created project. The
last of the four CRUD operations we have to look at is the deletion of projects:
bellybutton.destroy
The calling of destroy gets transmitted via DELETE and results in the deletion of the
project on the server:
DELETE /projects/2.xml
ActiveResource uses all of the four HTTP verbs as appropriate for REST. It offers
a very good client-side abstraction of REST resources. Additionally, many other
known methods of ActiveRecord work in ActiveResource, such as finding all in-
stances of a resource:
Project.find(:all).each do |p|
puts p.name
end
1.17 Finally
You don’t have to use REST everywhere. Hybrid solutions are conceivable and can
easily be implemented. Typically, you’re in the middle of a project when new Rails
features appear. It’s not a problem to develop single REST-based models and their
dedicated controllers to gain some experience. When starting a new application from
scratch, think about doing it fully in REST from the beginning. The advantages are
clear: a clean architecture, less code and multi-client capability.
Bibliography
[1] Ralf Wirdemann, Thomas Baustert: Rapid Web Development mit Ruby on Rails,
2. Auflage, Hanser, 2007
[2] Dave Thomas, David Heinemeier Hansson: Agile Web Development with Rails, Sec-
ond Edition, Pragmatic Bookshelf, 2006
[3] Curt Hibbs: Rolling with Ruby on Rails – Part 1,
http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html
[4] Curt Hibbs: Rolling with Ruby on Rails – Part 2,
http://www.onlamp.com/pub/a/onlamp/2005/03/03/rails.html