described_routes
DESCRIPTION
Features:
-
Dynamic, framework-neutral, client-friendly
ResourceTemplate
metadata describing the path/URI structures of your whole site or of specific resources -
A link header-based discovery protocol, enabling clients to find
ResourceTemplate
metadata from the resources of any enabled controller -
Easy integration with Rails
-
JSON, YAML and XML formats, also a bonus plain text report
ATTENTION: 0.8.0 adds Rails integration via Rack middleware; the Rails controller and helpers are hereby deprecated!
INSTALL:
sudo gem install mynyml-rack-respond_to --source http://gems.github.com
sudo gem install described_routes
USAGE:
Build time
In your Rakefile:
require 'tasks/described_routes'
Then:
$ rake --tasks described_routes
rake described_routes:json # Describe resource structure in JSON format
rake described_routes:xml # Describe resource structure in XML format
rake described_routes:yaml # Describe resource structure in YAML format
rake described_routes:text # Describe resource structure in text (comparable to "rake routes")
The JSON, XML and YAML formats (of which the YAML is the most readable) are designed for program consumption. The more human-friendly text output looks like this:
$ rake --silent described_routes:text
root root /
admin_products admin_products GET, POST /admin/products{-prefix|.|format}
new_admin_product new_admin_product GET /admin/products/new{-prefix|.|format}
{product_id} admin_product GET, PUT, DELETE /admin/products/{product_id}{-prefix|.|format}
edit edit_admin_product GET /admin/products/{product_id}/edit{-prefix|.|format}
described_routes described_routes GET, POST /described_routes{-prefix|.|format}
new_described_route new_described_route GET /described_routes/new{-prefix|.|format}
{route_name} described_route GET, PUT, DELETE /described_routes/{route_name}{-prefix|.|format}
edit edit_described_route GET /described_routes/{route_name}/edit{-prefix|.|format}
pages pages GET, POST /pages{-prefix|.|format}
new_page new_page GET /pages/new{-prefix|.|format}
{page_id} page GET, PUT, DELETE /pages/{page_id}{-prefix|.|format}
edit edit_page GET /pages/{page_id}/edit{-prefix|.|format}
summary summary_page GET /pages/{page_id}/summary{-prefix|.|format}
toggle_visibility toggle_visibility_page POST /pages/{page_id}/toggle_visibility{-prefix|.|format}
users users GET, POST /users{-prefix|.|format}
new_user new_user GET /users/new{-prefix|.|format}
{user_id} user GET, PUT, DELETE /users/{user_id}{-prefix|.|format}
edit edit_user GET /users/{user_id}/edit{-prefix|.|format}
articles user_articles GET, POST /users/{user_id}/articles{-prefix|.|format}
new_user_article new_user_article GET /users/{user_id}/articles/new{-prefix|.|format}
recent recent_user_articles GET /users/{user_id}/articles/recent{-prefix|.|format}
{article_id} user_article GET, PUT, DELETE /users/{user_id}/articles/{article_id}{-prefix|.|format}
edit edit_user_article GET /users/{user_id}/articles/{article_id}/edit{-prefix|.|format}
profile user_profile GET, PUT, DELETE, POST /users/{user_id}/profile{-prefix|.|format}
edit edit_user_profile GET /users/{user_id}/profile/edit{-prefix|.|format}
new new_user_profile GET /users/{user_id}/profile/new{-prefix|.|format}
You can specify the base URL of your application by appending “BASE=http://…” to the rake command line. The output will then include full URI templates.
Run time
In your config/environment.rb, add the DescribedRoutes::Middleware::Rails middleware in the Rails::Initializer.run
block:
require 'described_routes/middleware/rails'
Rails::Initializer.run do |config|
config.middleware.use DescribedRoutes::Middleware::Rails
end
There are two integration steps for run time support:
You (or your client application) can now browse to any of the following top level addresses:
-
…/described_routes
-
…/described_routes.txt
-
…/described_routes.json
-
…/described_routes.xml
-
…/described_routes.yaml
and for the named route “users” (say):
-
…/described_routes/users
-
…/described_routes/users.txt
-
…/described_routes/users.json
-
…/described_routes/users.xml
-
…/described_routes/users.yaml
In the absence of content negotiation, requests to addresses without format extensions redirect to the respective .txt address.
Note that
Example:
$ curl http://localhost:3000/described_routes/users.txt
users users GET, POST http://localhost:3000/users{-prefix|.|format}
new_user new_user GET http://localhost:3000/users/new{-prefix|.|format}
{user_id} user GET, PUT, DELETE http://localhost:3000/users/{user_id}{-prefix|.|format}
edit edit_user GET http://localhost:3000/users/{user_id}/edit{-prefix|.|format}
articles user_articles GET, POST http://localhost:3000/users/{user_id}/articles{-prefix|.|format}
new_user_article new_user_article GET http://localhost:3000/users/{user_id}/articles/new{-prefix|.|format}
recent recent_user_articles GET http://localhost:3000/users/{user_id}/articles/recent{-prefix|.|format}
{article_id} user_article GET, PUT, DELETE http://localhost:3000/users/{user_id}/articles/{article_id}{-prefix|.|format}
edit edit_user_article GET http://localhost:3000/users/{user_id}/articles/{article_id}/edit{-prefix|.|format}
profile user_profile GET, PUT, DELETE, POST http://localhost:3000/users/{user_id}/profile{-prefix|.|format}
edit edit_user_profile GET http://localhost:3000/users/{user_id}/profile/edit{-prefix|.|format}
new new_user_profile GET http://localhost:3000/users/{user_id}/profile/new{-prefix|.|format}
Partial template expansion
Any query parameters passed to the controller will be used to pre-populate the templates. In this example, the article_id
and format
parameters have been replaced, leaving article_id
:
$ 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. Their addresses can be referenced in <link>
elements in the <head>
section of an HTML page or (better) in HTTP headers, so any resource - regardless of format - can easily link to its own instance-specific metadata.
JSON example (after pretty printing):
$ 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"]
}
]
}
Link Header Discovery Protocol
A discovery protocol based on link headers is added automatically by the middleware (controller changes are no longer required). This protocol is understood by path-to
(enabling client APIs to be bootstrapped easily) and the link headers can be regarded as adding useful type information to resources.
Regular resources are given a link header that points to that resource’s ResourceTemplate
metadata. That in turn is given a link header that points to the ResourceTemplates
metadata for the entire application. The root resource has a link header that points to the ResourceTemplates
metadata directly.
For further information on link headers, see the draft spec tools.ietf.org/id/draft-nottingham-http-link-header-06.txt and the link_header
gem.
DATA STRUCTURES and FORMATS
Natural structure
The YAML and JSON representations appear as simple array and hash structures. Each resource is represented by a hash of attributes (one of which may be a list of child resources); the top level structure is an array of parentless resources.
Attributes:
name
-
A Rails-generated route name
rel
-
An indication of a child resource’s relationship to its parent
options
-
A list of HTTP methods supported by the resource
path_template
-
A template for the resource’s path, in the style of URI Template but as a relative path
uri_template
-
A template for the resource’s URI (generated only if the root URI is known at generation time)
params
-
A list of parameters required by path_template
optional_params
-
A list of optional parameters that may be incorporated by the path_template
Empty or blank attributes are omitted.
Note that only named routes are considered. Pre-Rails 2.3 “formatted routes” are explicitly excluded, and for Rails 2.3 onwards, "format"
is the only entry likely to appear in the optional_parameters attribute.
XML
This follows the natural structure but with the following modifications:
-
A
ResourceTemplate
element for each resource template -
A
ResourceTemplates
element for each list of resources (top level or subresources) -
Params
andOptionalParams
elements forparams
andoptional_params
, each containingparam
elements -
A single
options
element contains the applicable HTTP methods as a comma-separated list
Calls to parse_xml will at present result in NoMethodError exceptions being raised.
Samples
See test_rails_app/test/fixtures
for sample outputs in each of the supported formats.
CUSTOMISATION
It is possible to customise the data collected from Rails, for example to hide sensitive routes:
DescribedRoutes::RailsRoutes.parsed_hook = lambda {|a| a.reject{|h| h["name"] =~ /^admin/}}
This hook operates on the raw “parsed” (Array/Hash) data before conversion to ResourceTemplate objects.
REQUIREMENTS:
Rails, for the Rake tasks and Rails controller. The ResourceTemplate class and its formats are however Rails-independent.
The addressable gem, 2.1.0 or above. This is now available as a regular gem install from Rubyforge.
Author
Mike Burrows (asplake), email [email protected], website positiveincline.com (see articles tagged described_routes)