described_routes
DESCRIPTION
Framework-neutral metadata, describing (among other things) your Rails routes in JSON, YAML, XML and plain text formats.
Also the home (for now at least) of the ResourceTemplate class.
See roadmap for described_routes and path-to at positiveincline.com/?p=213.
SYNOPSIS:
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:yaml_short # Describe resource structure in YAML format (basic structure only)
rake described_routes:text # Describe resource structure in text (comparable to "rake routes")
The 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.
The JSON, XML and YAML formats (of which the YAML is the most readable) are designed for program consumption.
Run time
Include the controller, perhaps in an initializer:
require 'described_routes/rails_controller'
Add the following route in config/routes.rb:
map.resources :described_routes, :controller => "described_routes/rails"
You (or your client application) can now browse to any of the following top level addresses:
-
…/described_routes.json
-
…/described_routes.xml
-
…/described_routes.yaml
-
…/described_routes.yaml?short=true
-
…/described_routes.ytxt
and for the named route “users” (say):
-
…/described_routes/users.json
-
…/described_routes/users.xml
-
…/described_routes/users.yaml
-
…/described_routes/users.yaml?short=true
-
…/described_routes/users.txt
If the application has a route named “root”, run-time-generated data will include uri_template attributes based on root_url in addition to the path_template attributes supported at build time.
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"]
}
]
}
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.
INSTALL:
sudo gem install described_routes
Author
Mike Burrows (asplake), email [email protected], website positiveincline.com (see articles tagged described_routes)