Chapter 4. The REST of the World
REST is an architectural abstraction designed for distributed systems and the Web. Actions in REST map to URIs that identify resources. REST operates on resource representations that can differ from the data representations stored in the application database. A resource epresentation can be anything from a JSON string to the HTML returned when accessing a certain URL. The data object stored in a database record is something fundamentally different.
Life Is CRUD
Actions on database records are usually guided not by REST principles, but by CRUD. CRUD is an abbreviation of Create, Read, Update, Delete, the four basic functions of persistent storage.
Sometimes an additional function is considered: Search. In this case, CRUD is referred to as SCRUD.
Note
Wikipedia suggests that CRUD is rather a backronym. A backronym is a specially constructed acronym created to fit a specific word or concept. A famous backronym example is NASA’s Combined Operational Load-Bearing External Resistance Treadmill (COLBERT), named after Stephen Colbert.
The actions CRUD refers to map to all of the major functions implemented in relational database applications. In fact, each letter maps to a standard SQL statement.
REST and CRUD are therefore two very different paradigms for two very different worlds.
REST is an architectural style for applications. REST resources can be the result of different database queries, and neither the client nor the REST server really needs to know how these queries are constructed or where the data comes from.
REST applications generally assume that they live in a REST world where everyone speaks their language, and therefore will delegate to some other component (known as middleware) the tasks of interfacing with a non-REST database and fetching the requested data. Figure 4-1 illustrates this paradigm.
RESTful Rails
The Rails router is responsible for redirecting incoming requests to controller actions.
The routing module provides URL rewriting in native Ruby. It recognizes URLs and dispatches them as defined in config/routes.rb. The routes file can therefore be considered as a map for your application requests. The map (or the router) tells each request where to go based on some predefined patterns.
The default resource routing in Rails allows you to declare all the basic routes for a certain controller with a single line of code. This means you do not have to declare all your routes separately for actions like index, show, new, edit, create, update, and destroy. The resourceful route will take care of that.
In Rails, resourceful routes provide the mapping between HTTP verbs and URLs and controller actions. These actions in turn map to CRUD operations in the database.
When a Rails application receives an HTTP request with a specific HTTP method such as GET, POST, PATCH, PUT, or DELETE, it asks the router to map it to a controller action. If the route is matched, Rails will dispatch that request.
An entry in the routes file of the form:
resources
:categories
creates by default seven different application routes mapping to the categories controller, as shown in Table 4-1.
HTTP verb | Path | Controller#Action | Result |
---|---|---|---|
GET | /categories | categories#index |
Displays all categories |
GET | /categories/new | categories#new |
Returns an HTML form for creating a new category |
POST | /categories | categories#create |
Creates a new category |
GET | /categories/:id | categories#show |
Displays a specific category |
GET | /categories/:id/edit | categories#edit |
Returns an HTML form for editing a category |
PATCH/PUT | /categories/:id | categories#update |
Updates a specific category |
DELETE | /categories/:id | categories#destroy |
Deletes a specific category |
Note
Note that the mapping provided by the default resourceful route uses both HTTP verbs and URLs to match inbound requests. Therefore, four URLs correspond to seven different actions.
Some of the routes generated through the resourceful route mechanism are not always necessary. For example, an API application would not pass an HTML form to edit a resource; instead, it would use a PATCH/PUT request with the parameters that need to be modified. So, there are situations in which you might want to redefine routes according to your needs. It is important to generate only the routes that your application actually needs, to cut down on memory usage and speed up the routing process in your server.
You can define single routes by specifying the route and the action to be matched:
get
'/categories/:id'
,
:to
=>
'categories#show'
The HTTP helper methods GET, POST, PATCH, PUT, and DELETE can be used for routes that do not fit the default resourceful route.
If your route needs to respond to more than one HTTP method (or all methods), then using the :via
option on match is preferable:
match
'categories/:id'
=>
'categories#show'
,
via
:
[
:get
,
:post
]
This way, if you POST to /categories/:id, it will route to the create_category action, while a GET on the same URL will route to the show action. It’s as simple as that.
Note
Rails routes are matched in the order they are specified. If you have a resources :categories
above a get 'categories/graph'
the show action’s route for the resources line will be matched before the get line. You can move the get line above the resources line to match it first.
You can also use the :only
and :except
options to fine-tune the default Rails behavior of creating routes for the seven default actions (index, show, new, create, edit, update, and destroy). The :only
option tells Rails to create only the specified routes:
resources
:categories
,
only
:
[
:index
,
:show
]
The :except
option specifies a route or list of routes that Rails should not create:
resources
:categories
,
except
:
[
:destroy
]
Note
There is a lot more than meets the eye hidden in the Rails routing module. Refer to the Ruby on Rails documentation and the Ruby on Rails Guides for more information.
Testing RESTful Routing in Rails
Routes in Rails can easily be tested to make sure that the mappings you write in config/routes.rb produce the results you expected.
Once routes have been created for your application you can visit http://localhost:3000/rails/info/routes in your browser while your server is running in the development environment to get a complete list of the available routes. You can also execute the following command in a terminal window:
$
rake
routes
It will produce the same output.
Rails also offers three built-in assertions designed to make testing routes simpler:
assert_generates
assert_recognizes
assert_routing
With assert_generates
we can verify that a particular set of options generates a particular path. It can be used with default routes or custom routes:
assert_generates
'/categories/1'
,
{
controller
:
'categories'
,
action
:
'show'
,
id
:
'1'
}
assert_generates
'/about'
,
controller
:
'pages'
,
action
:
'about'
With assert_recognizes
we can verify that a given path is recognized and routed accordingly:
assert_recognizes
({
controller
:
'categories'
,
action
:
'show'
,
id
:
'1'
},
'/categories/1'
)
Finally, assert_routing
checks the route both ways—it tests that the path generates the options and that the options generate the path:
assert_routing
({
path
:
'categories'
,
method
:
:post
},
{
controller
:
'categories'
,
action
:
'create'
})
HTTP Semantics
HTTP verbs inherently have their own semantics, so it can become confusing if we try to map verbs in HTTP (and therefore REST applications) one-to-one to CRUD actions.
In general, CRUD actions are mapped to the following HTTP actions:
- Create = PUT/POST
- Read = GET
- Update = POST/PUT
- Delete = DELETE
So when do you use PUT, and when do you use POST? As we’ve seen, in Rails a resource route maps HTTP verbs and URLs to controller actions. Each action also maps to default CRUD actions. If you use resourceful routes, you will use POST to create resources and PUT to update them.
The mappings created with resourceful routes are RESTful. The problem arises when you are creating not-so-standard routes to handle specific controller actions.
This does not mean that CRUD and REST have no correlation whatsoever when things get complicated. It has been shown that CRUD clients can interact perfectly well with REST services; they just have different ideas on when it is appropriate to use PUT versus POST. In addition, you need to add some additional logic for the mapping to be considered a complete transformation from one space (REST and resources land) to the other (CRUD and the kingdom of persistent storage).
Generally speaking, Read maps to an HTTP GET request and Delete corresponds to an HTTP DELETE operation. What we are left with is PUT and POST and the Update and Create actions. The problem here is that in some cases Create means PUT, but in other cases it means POST, and in some cases Update means POST, while in others it means PUT.
More specifically, the problem lies in the definition of the POST and PUT methods in the HTTP protocol.
The HTTP/1.1 specification states that the POST method “is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.”
According to the spec, the POST method can be used to cover the following functions:
- Annotation of existing resources
- Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles
- Providing a block of data, such as the result of submitting a form, to a data-handling process
- Extending a database through an append operation
Conversely, the PUT method “requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.”
You can see why this is confusing—PUT and POST could almost be used interchangeably, and no one would notice.
As we have seen, Rails uses POST for creating resources and PUT to modify them in its default resourceful route. You can of course redefine the default routes, but the bottom line is that the difference between PUT and POST is quite subtle and in the end lies in the definition of idempotency.
HTTP/1.1 defines two types of methods: safe methods and idempotent methods. Safe methods are HTTP methods that do not modify resources. The specification warns developers of the established convention that the “GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval” and states that these methods “ought to be considered ‘safe’”—i.e., they do not modify the resources.
Idempotent methods are those that posses the property of “idempotence.” An action is said to be idempotent if n repetitions of that action result in the same resource state as performing the action a single time.
The methods GET, HEAD, PUT, and DELETE are idempotent, as are the methods OPTIONS and TRACE (which should not have side effects).
PUT is idempotent, so if you PUT an object twice, it has no effect. This is a nice property, so use PUT when possible.
POST is not idempotent, but this doesn’t mean it shouldn’t be used for creating resources.
The magical rule of thumb is:
- If you are going to create a resource for which you already know the URL, it is better to use PUT: e.g.,
PUT <resource> /user/profile
. - If you are going to create a resource by sending some data to its resource controller, use POST instead and have the controller (or some other component) generate the corresponding URL for the newly created resource: e.g.,
POST <resource> /categories/
.
The spec describes the difference between the two request types as follows:
The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request—the user agent knows what URI is intended and the server MUST NOT apply that request to some other resource. If the server desires that the request be applied to a different URI, it MUST send a 301 (Moved Permanently) response; the user agent MAY then make its own decision regarding whether or not to redirect the request.
Note
Note that it is possible for a sequence of several requests to be nonidempotent, even if all of the methods executed in that sequence are idempotent.
A sequence of actions is considered idempotent if execution of the entire sequence always yields a result that is not changed by a reexecution of all (or part) of that sequence. For example, we say that a sequence is nonidempotent if its result depends on a value that is later modified in the same sequence.
A sequence of actions that never has side effects on the server side is idempotent by definition (provided that no concurrent operations are being executed on the same set of resources).
We are now going to dive into the individual HTTP methods and what they do. Those of you who work with the HTTP protocol on a daily basis may want to skip this material. Otherwise, if you want to learn more about how HTTP methods are mapped to controller actions in Rails, keep reading.
GET
The GET method is used to retrieve information in the form of an entity identified by the Request-URI. The HTTP/1.1 specification also specifies that if the Request-URI refers to a process producing data, the data produced is what will be returned as the entity in the response, and not the source text of the process, unless the same text happens to also be the output of the process.
It may be possible to use a conditional GET if the request message includes an If-Modified-Since
, If-Unmodified-Since
, If-Match
, If-None-Match
, or If-Range
header field. Conditional GETs provide a way for web servers to tell browsers that the response to a GET request hasn’t changed since the last request and can be safely pulled from the browser cache. In this case the entity is transferred only under the conditions sent through the conditional header fields.
In Rails the conditional GET request works by using the HTTP_IF_NONE_MATCH
and HTTP_IF_MODIFIED_SINCE
headers. These pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last-modified timestamp matches the server’s version, then the server only needs to send back an empty response with a “not modified” status. This allows the client to refresh cached entities without performing multiple requests.
We will see how these methods can be used in Rails when we look at how to implement caching and how to reduce unnecessary network usage.
HEAD
The HEAD method is identical to the GET method, except that with HEAD the server does not return a message body in the response. According to the spec, “The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request.” The HEAD method is therefore used to obtain metainformation about the entity without transferring the entity body.
It is possible to handle the HEAD method in Rails by using the head?()
function:
# Is this a HEAD request?
# Equivalent to <tt>request.request_method_symbol == :head</tt>.
def
head?
HTTP_METHOD_LOOKUP
[
request_method
]
==
:head
end
The response to a HEAD request can be cached, so the information it contains may be used to update a previously cached entity if the new values indicate that the cached entity has changed (i.e., if the values of the Content-Length
, Content-MD5
, ETag
, or Last-Modified
headers have changed).
POST
According to the HTTP specification, “The actual function performed by the POST method is determined by the server and is usually dependent on the Request-URI.” The notion of subordinate resources is important for the POST method: the posted entity is subordinate to the Request-URI in the same way that a file is subordinate to the directory containing it.
If the POST method results in a resource being created on the origin server, it should return a 201 (Created) response. In this case the response should also contain an entity that describes the status of the request and refers to the new resource, as well as a Location header. This can be used to redirect the recipient to a location other than the Request-URI to complete the request or identify a new resource. In the case of a 201 (Created) response, it points to the location of the new resource created by the request. For 3xx responses, it should instead indicate the server’s preferred URI for automatic redirection to the resource.
Responses to the POST method cannot be cached, unless they include the appropriate Cache-Control
or Expires
header fields. In this case, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.
PUT
Through the PUT method, we can make a request to store the enclosed entity under the supplied Request-URI. If the Request-URI already exists, the entity enclosed in the request is considered a modified version of the existing one, and it replaces the version stored on the server. If the Request-URI does not point to an existing resource, a new resource with that URI is created.
When a new resource is created, the origin server must send a 201 (Created) response. When an existing resource is modified, the response sent to indicate successful completion of the request should be either 200 (OK) or 204 (No Content).
In the case that the resource could not be created or modified, the server should send an appropriate error response indicating the nature of the problem.
As with POST, responses to the PUT method are not cacheable.
DELETE
The DELETE method is used to request that the origin server delete the resource identified by the Request-URI.
With a DELETE request the client cannot be guaranteed that the operation has been carried out, even if the server returns a status code indicating that the action has been completed successfully. That said, the server should not indicate success unless it intends to either delete the resource or move it to an inaccessible location.
The server can send a 200 (OK) or 204 (No Content) response in the case of success, or a 202 (Accepted) response if the instruction has been accepted but the action has not yet been enacted.
Responses to the DELETE method are not cacheable.
Wrapping Up
In this chapter we have learned about the semantics of the HTTP methods, and how these can be used in different situations. We have also seen how HTTP methods can be mapped to Rails actions even when we are not using resourceful routes. In the next chapter we will start designing hypermedia APIs: you will learn how to reason about the application flow and to think about resources as you would think about web pages.
Get RESTful Rails Development now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.