In between a trip to the local dump, another meeting with HR (role officially redundant now, though still on the payroll for a few weeks) and getting our signatures witnessed for our house sale, I added partial template expansion to described_routes today. This is potentially a lot more important than it sounds, but before I go into that, an explanation of the functionality.
What it does
Any query parameters passed to the included Rails controller will be used to pre-populate the generated resource templates and their URI templates. In this example, the <code>article_id</code> and <code>format</code> parameters have been replaced with actual values, leaving the <code>article_id</code> unchanged:
$ curl "http://localhost:3000/described_routes/user.text?format=json&user_id=dojo"
user user GET, PUT, DELETE http://localhost:3000/users/dojo.json
edit edit_user GET http://localhost:3000/users/dojo/edit.json
articles user_articles GET, POST http://localhost:3000/users/dojo/articles.json
new_user_article new_user_article GET http://localhost:3000/users/dojo/articles/new.json
recent recent_user_articles GET http://localhost:3000/users/dojo/articles/recent.json
{article_id} user_article GET, PUT, DELETE http://localhost:3000/users/dojo/articles/{article_id}.json
edit edit_user_article GET http://localhost:3000/users/dojo/articles/{article_id}/edit.json
profile user_profile GET, PUT, DELETE, POST http://localhost:3000/users/dojo/profile.json
edit edit_user_profile GET http://localhost:3000/users/dojo/profile/edit.json
new new_user_profile GET http://localhost:3000/users/dojo/profile/new.json
More typically, JSON, YAML or XML format would be requested. Here’s a JSON example (after pretty printing – the original output was all on one line):
$ curl "http://localhost:3000/described_routes/user_articles.yaml?user_id=dojo&format=json"
{
"name":"user_articles",
"rel":"articles",
"path_template":"/users/dojo/articles.json",
"uri_template":"http://localhost:3000/users/dojo/articles.json",
"options":["GET", "POST"],
"resource_templates":[
{
"name":"new_user_article",
"options":["GET"],
"path_template":"/users/dojo/articles/new.json",
"uri_template":"http://localhost:3000/users/dojo/articles/new.json",
"rel":"new_user_article"
},
{
"name":"recent_user_articles",
"options":["GET"],
"path_template":"/users/dojo/articles/recent.json",
"uri_template":"http://localhost:3000/users/dojo/articles/recent.json",
"rel":"recent"
},
{
"name":"user_article",
"resource_templates":[
{
"name":"edit_user_article",
"options":["GET"],
"path_template":"/users/dojo/articles/{article_id}/edit.json",
"uri_template":"http://localhost:3000/users/dojo/articles/{article_id}/edit.json",
"rel":"edit",
"params":["article_id"]
}
],
"options":["GET", "PUT", "DELETE"],
"path_template":"/users/dojo/articles/{article_id}.json",
"uri_template":"http://localhost:3000/users/dojo/articles/{article_id}.json",
"params":["article_id"]
}
]
}
Putting it to use, and why it’s important
Up to now, we’ve focussed on describing whole applications, providing client applications with metadata that helps them navigate the application effectively, even driving client APIs such as those generated dynamically by path-to. This is an efficient way of doing things and it maps well onto the way familiar web APIs work now, but can we unify it with the RESTful concepts around hypertext (HATEOAS and all that)? I think we can.
Imagine putting in the <head> section of the HTML representation of John Doe’s article collection at http://localhost:3000/users/dojo/articles a <link> element that references its own specific metadata at http://localhost:3000/described_routes/user_articles.json?user_id=dojo&format=json. A client reading that document can follow that link and now has in its possession the recipe for accessing all of the resources that belong to that collection – whether through complete URIs (e.g. for recent articles) or through parameterised templates (e.g. for specific articles, identified by id).
We can improve on this by returning these resource-specific links in HTTP headers. This makes them independent of the representation format (<head> and <link> are HTML-specific) and will work even on requests that return no body at all, HEAD in particular. At the cost of one header (easily suppressed for clients that don’t need it), every resource is uniquely enhanced, minimising the amount of state that needs to be retained in the client. For very lightweight clients (e.g. JavaScript libraries embedded in the browser) or for applications lacking a stable overall structure, this could be very useful indeed.
A mini path-to app centered on Mr Doe’s article collection could look something like this:
require "path-to/described_routes"
articles = PathTo::DescribedRoutes.bootstrap(
"http://localhost:3000/users/dojo/articles")
articles.recent.get.first
articles["hateoas-and-other-7-letter-acronyms"].get
articles.post("title" => "The power of reflection", ...)
Here the bootstrap method creates an articles object that has an API that follows the structure given in the metadata discovered via headers found at http://localhost:3000/users/dojo/articles. Easy!