REST and Form Retrieval
Tuesday, November 21st, 2006I have been converting a small Rails application to REST this past week and several issues have come to light surrounding HTML forms. I can’t seem to find a good solution to these problems. First, I want to tackle delivering of arbitrary forms to HTML clients.
In a simple application revolving around one resource, delivering a form to an HTML client (to query against that resource, for example) is a “classic” task. With REST, the single resource has straightforward URIs for all the classic CRUD operations. But the forms that facilitate the CRUD operations are much less clear. For example, if I wish to fetch the clown with ID 1, I can issue an HTTP request like this:
http://example.domain/clowns/1 (HTTP verb GET)
And to update the clown:
http://example.domain/clowns/1 (HTTP verb PUT, with form-encoded attributes)
But how do I deliver the editing form to the client? The first URL in the example only delivers the clown resource itself, not the form to edit the clown. One approach is to treat the form itself as a resource (see reference 2) and allow it to be RESTfully manipulated:
http://example.domain/clownEditForm (HTTP verb GET)
As clean as this looks from the URL perspective, it is ugly in Rails. The resource route mapping available in the recent Simply RESTful Edge Rails enhancements does not permit a second named resource using the same HTTP verb (GET) to use the same controller -so the ClownController couldn’t serve up this form and you would need an additional controller -yech. Another approach is to use a representational tweak via a view parameter:
http://example.domain/clowns/1?view=edit (HTTP verb GET)
In a similar vein, Rails supports view tweaks to the URL with an abbreviated syntax. So to fetch clown ID 1 represented for editing, I would use a URL like this:
http://example.domain/clowns/1;edit (HTTP verb GET)
OK, I can grasp what is going on here. But extending this model is not so easy. For example, what URL would I use for delivering an HTML form to find the closest clowns to a given address? The problem is that I am not delivering a special representation of a clown. In fact, there is no clown at all in this form -just an address field. And I’ll be damned if I am going to create a one-shot address resource (and controller) for this purpose. So where does this leave me? I’ve opted for representational tweaks to the collection resource:
http://example.domain/clowns;queryByAddressForm (HTTP verb GET)
And the corresponding routes.rb declaration:
map.resources :clowns, :collection => {:queryByAddressForm => :get}
Not very satisfactory, but workable.
A twist: what if I want to use this same HTML page for displaying the results of the query (without AJAX) and the query form itself? Now the concept of getting the form and getting the query results have blended into one. This construct is useful in HTML even if it is absurd for a web service or JSON/XML delivery. What to do? I went to this intentionally vague representation:
http://example.domain/clowns;queryByAddress (HTTP verb GET)
This HTTP request will return a page with the empty form at the top and the results (initially empty) at the bottom. The form submits with a GET to the same URL -but this time with a address parameter that causes results (and previous form values) to be included in the returned page:
http://example.domain/clowns;queryByAddress?address=3100+Edgewater+27514 (HTTP verb GET)
In a more general sense, this may be an acceptable paradigm: a GET to a query URL requesting HTML but without the necessary parameters is interpreted as a request for the form. Can I get an “Amen, Brother!” on that? Can I get some built-in Rails support?
At this point I am willing to settle. But I’m still not happy. Part of my problem may be that Rails has not yet made it truly easy to do REST 100% right. A more strictly RESTful approach could be implemented, but for now Edge Rails is using the KISS approach.
