Positive Incline Mike Burrows (@asplake) moving on up, positively

February 10, 2010

Experimental {.format} in Routes

Filed under: Web Integration — Tags: , , , — Mike @ 7:34 pm

A while back I raised on the URI Template mailing list the idea of a {.var} syntax that generates optional path extensions, “.html”, “.json”, etc.

For example, given a path template of /releases/{id}/notes{.format} and an id parameter of “1”, paths of /releases/1/notes or /releases/1/notes.pdf will be generated depending on whether the supplied format parameters are undefined or “pdf” respectively.

It’s not a completely original idea.  Rails has a (.:format) syntax that DescribedRoutes maps to the flexible but somewhat clunky {-prefix|.|format} syntax of the old draft URI Template spec. The proposed syntax seems more in keeping with current draft (very much a work in progress) though.

Today my Routes fork gained support for the syntax and tweaks to the new SubMapper helpers so that duplicate “formatted routes” aren’t generated (something that happened to Rails a long time ago).

Example config (<code>config/routing.py</code> in your Pylons app):

with mapper.collection(
                requirements={'id': 'd+'}) as c:
    c.member.link(rel='notes', name='release_notes')

And in the paster shell:

>>> print mapper
Route name     Methods Path
releases       GET     /releases{.format}
create_release POST    /releases{.format}
new_release    GET     /releases/new{.format}
release        GET     /releases/{id}{.format}
update_release PUT     /releases/{id}{.format}
delete_release DELETE  /releases/{id}{.format}
edit_release   GET     /releases/{id}/edit{.format}
release_notes  GET     /releases/{id}/notes{.format}
>>> url('release_notes', id=1, format='pdf')
>>> url('release_notes', id=1)
>>> mapper.match('/releases/1/notes')
{'action': u'notes', 'controller': u'release', 'id': u'1', 'format': None}
>>> mapper.match('/releases/1/notes.pdf')
{'action': u'notes', 'controller': u'release', 'id': u'1', 'format': u'pdf'}

Just add formatted=False to your collection() call if you don’t want this new behaviour.

I haven’t sent the Routes guys a pull request yet. I’d like some feedback first, and meanwhile I will try to come up with a nice helper that takes the format extension to override content negotiation. My app has some basic header-based conneg already but I would like the convenience of being able to override it in the URL.

December 21, 2009

DRY up your routes – a Pylons routing refactoring

Filed under: Programming,Web Integration — Tags: , , , , , — Mike @ 11:43 am


This post is in several acts, each one a refactoring. Taking the stage will be some familiar-looking application code; behind the scenes lurks some enhanced framework code that makes the refactorings possible.

Our story starts with an old-school (pre-REST) routing configuration. It’s in Python, for Pylons and other Routes-based web frameworks, but let me reassure the Ruby audience that Routes borrows very heavily from Rails and their routing configurations look quite similar.

We will finish with collection(), an experimental (but I hope worthy) alternative to resource(), the routing helper that rose to prominence when Rails first made its big push to REST.

The starting point: textbook routing.py

from routes import Mapper

def make_map():


    # Releases - Collection
    mapper.connect('releases', '/releases',
              controller='release', action='index', conditions=dict(method='GET'))
    mapper.connect('create_release', '/releases',
              controller='release', action='create', conditions=dict(method='POST'))
    mapper.connect('new_release', '/releases/new',
              controller='release', action='new', conditions=dict(method='GET'))

    # Releases - Members
    mapper.connect('release', '/releases/{id}',
              controller='release', action='show',
              requirements=dict(id='d+'), conditions=dict(method='GET'))
    mapper.connect('update_release', '/releases/{id}',
              controller='release', action='update',
              requirements=dict(id='d+'), conditions=dict(method='PUT'))
    mapper.connect('delete_release', '/releases/{id}',
              controller='release', action='delete',
              requirements=dict(id='d+'), conditions=dict(method='DELETE'))
    mapper.connect('edit_release', '/releases/{id}/edit',
              controller='release', action='edit',
              requirements=dict(id='d+'), conditions=dict(method='GET'))
    mapper.connect('release_notes', '/releases/{id}/notes',
              controller='release', action='release_notes',
              requirements=dict(id='d+'), conditions=dict(method='GET'))

Full of duplication, verbose, even ugly. Just because it sits in a config directory, does it have to look that bad?

Refactoring 1: Introducing submapper()

New in Routes 1.11 (so really quite new), submapper()provides the means to pull out parameters previously shared across multiple connect() calls.

One particularly nice feature is that the SubMapper objects returned by submapper() support the Python managed object protocol so if you’re on Python 2.5 or above you can use them with the with syntax like this:

from release_tool.lib.mapper import Mapper

def make_map():


    # Releases - Collection
    with mapper.submapper(
                    path_prefix='/releases') as c:
        c.connect('releases', '', action='index',
        c.connect('create_release', '', action='create',
        c.connect('new_release', '/new', action='new',

    # Releases - Members
    with mapper.submapper(
                    requirements=dict(id='d+')) as m:
        m.connect('release', '', action='show',
        m.connect('update_release', '', action='update',
        m.connect('delete_release', '', action='delete',
        m.connect('edit_release', '/edit', action='edit',
        m.connect('release_notes', '/notes', action='release_notes',

That’s a definite improvement (and credit where credit is due – submappers are great innovation, though a small bug requires our local extensions to be imported here), but notice that there’s still some duplication between the two submappers blocks, the first one corresponding to the collection resource, the second to that same collection’s members. Wouldn’t it be cool if we could nest them?

Refactoring 2: Submapper nesting

On the surface, the Routes 1.11 API doesn’t appear to support submapper nesting, but the SubMapper objects do indeed nest. Adding a submapper() method to SubMapper is a trivial change, and it take only minor internal tweaks for deeper nestings to function correctly.

    # Releases
    with mapper.submapper(
                    path_prefix='/releases') as c:
        # Collection
        c.connect('releases', '', action='index',
        c.connect('create_release', '', action='create',
        c.connect('new_release', '/new', action='new',
        # Members
        with c.submapper(
                    requirements=dict(id='d+')) as m:
            m.connect('release', '', action='show',
            m.connect('update_release', '', action='update',
            m.connect('delete_release', '', action='delete',
            m.connect('edit_release', '/edit', action='edit',
            m.connect('release_notes', '/notes', action='release_notes',

We seem to be on to something here, but what about the repetition (in one guise or another) of the resource name “release”? And what’s with those strange empty string ('') parameters?

Refactoring 3: Links and actions

To fix the repetition issue it’s clear that we need helpers that will generate those connect() calls for us more intelligently. But before we do that, let’s recognise that there are really two kinds of routes being generated here:

  1. Links to singleton subresources that support only one HTTP method, the most ubiquitous examples being the new and edit representations for which GET is the applicable HTTP method, but special subresources that are the targets for POST actions are common too;
  2. Actions that correspond directly to HTTP methods: index and create for GET and POST[1] on collection resources, show, update, delete for GET, PUT, DELETE on member resources.

It’s that second type that give rise to those strange empty strings as there is no further navigation to be done relative to the target resource.

So let’s add link() and action() helpers:

    # Releases
    with mapper.submapper(
                    path_prefix='/releases') as c:
        # Collection
        c.action(action='index', name='releases')
        c.action(action='create', method='POST')
        # Members
        with c.submapper(
                    requirements=dict(id='d+')) as m:
            m.action(action='show', name='release')
            m.action(action='update', method='PUT')
            m.action(action='delete', method='DELETE')
            m.link(rel='notes', name='release_notes')

Things are still moving in the right direction, but given that most of those links and actions follow a very well-worn convention, how about specific helpers for those?

Refactoring 4: More helpers

I’ll be honest – this is just an intermediate stage. You can see where we’re headed now though:

    # Releases
    with mapper.submapper(
                    path_prefix='/releases') as c:
        # Collection
        # Members
        with c.submapper(
                    requirements=dict(id='d+')) as m:
            m.link(rel='notes', name='release_notes')

Refactoring 5: Parameter-driven generation

We’ll be using those same names a lot across our resources, so let’s just identify the ones we want in a list:

    # Releases
    with mapper.submapper(
                    actions=['index', 'create', 'new']) as c:
        # Members
        with c.submapper(
                    actions=['show', 'update', 'delete', 'edit']) as m:
            m.link(rel='notes', name='release_notes')

But even this pattern will be repeated across resources, so let’s define a collection() helper that does it all:

Refactoring 6: Using the collection() helper

    with mapper.collection(
                        'requirements': {'id': 'd+'}}) as c:
        c.member.link(rel='notes', name='release_notes')

And we’re done.

Let’s compare this to the equivalent resource() call:

    # Releases
                # no requirements!
                member = {'notes':'GET'})

Superficially similar, but I’m unable to specify requirements on member paths and I don’t get to name my custom route either – the generated name “notes_release” just isn’t right! The bottom line is that resource() is approaching a dead end; its only way forward is to make its parameters ever more complex.

So… how about we put resource() on notice and bring in something more flexible and – dare I say – Pythonic? And wouldn’t it look just as nice in Ruby too? Answers on a postcard please…

August 4, 2009

described_routes is Rack middleware

Filed under: Web Integration — Tags: , , , , , , , — Mike @ 12:01 pm

Last week I received this:

#described_routes could make beautiful middleware

Why didn’t I think of that?

So I took a quick look at Rack, found there was almost nothing to learn, and over the weekend made the change. And it was well worth it: integrating described_routes into your Rails application is now much easier. There’s no need to modify your routes (the middleware recognizes and serves requests to /described_routes automatically) or your controllers (the discovery protocol’s link headers are added automatically to other requests). In fact the old integration method looks so ugly by comparison that I’ve deprecated it – it’s *that* embarrassing!

Now, run-time integration needs only this modification to your environment.rb‘s Rails::Initializer.run block:

require 'described_routes/middleware/rails'
Rails::Initializer.run do |config|
  config.middleware.use DescribedRoutes::Middleware::Rails

Revised instructions (compare with the old):

  1. Install the described_routes gem for the server
  2. Add build-time integration to the server (a one-liner to add some useful Rake tasks)
  3. Add run-time integration to the server (just the environment.rb modification above)
  4. Install and run path-to (for an “instant” client API)
  5. Profit!

Yes, it’s for Rack for Rails

The sharp-eyed reader will have noticed that despite the move to Rack, we’re still discussing a Rails integration. What about described_routes for other Rack-aware or Rack-based frameworks?

Most of the new middleware’s functionality exists in an abstract class DescribedRoutes::Middleware::Base but it needs two methods implemented for each framework:

  1. get_resource_templates – get named routes from the application and convert to ResourceTemplates
  2. get_resource_routing – map a request to a ResourceTemplate and its parameter list

In DescribedRoutes::Middleware::Rails:

  1. get_resource_templates hooks into described_routes code originally based on ‘rake routes’, and
  2. get_resource_routing extracts the controller name, action name and path parameters from a Rack environment member ‘action_controller.request.path_parameters’ populated by Rails as it processes the request – our stuff must happen afterwards.  The route name is reverse-mapped from the controller and action name.[1]

It should be clear now that both methods are necessarily framework-specific; indeed Rack does not provide routing itself.

The test of described_routes‘s underlying framework-neutrality will be its integration with a framework other than Ruby on Rails. This should be easier to achieve now than previously; perhaps someone with more knowledge and need than me will beat me to it (please be my guest!). I’m tempted meanwhile to double the challenge and attempt it in Python for WSGI-based frameworks (wish me luck!).

[1]Actually it’s a shame that Rails doesn’t make the request’s route name or route object available anywhere – is there anyone else who would use it?

June 25, 2009


Filed under: Web Integration — Tags: , , , , — Mike @ 1:49 pm

In my previous post I was casting around for an alternative to type as the name of a link header attribute, since in the standard this refers to a content type, not the “logical” type of the resource pointed to by the link.

I have decided to go with role, borrowed from XLink. I know that we’re not dealing with XML here and XLink isn’t wildly popular, but it’s documented, accepted and understood, so why reinvent the wheel?

Consistent with both XLink and the existing rel attributes, I’ve made these role attributes contain URIs, as in this example:

Link: <http://example.com/users/dojo>; rel="self"; role="http://example.com/described_routes#user",
           <http://example.com/described_routes/user>; rel="describedby"; role="http://example.com/described_routes#described_route",
           <http://example.com/described_routes/user?user_id=dojo>; rel="describedby"; role="http://example.com/described_routes#ResourceTemplate",
           <http://example.com/users>; rel="up"; role="http://example.com/described_routes#users",
           <http://example.com/users/dojo/edit>; rel="edit"; rel="http://example.com/described_routes/user#edit"; role="http://example.com/described_routes#edit_user",
           <http://example.com/users/dojo/articles>; rel="http://example.com/described_routes/user#articles"; role="http://example.com/described_routes#user_articles"

You can think of this in terms of entities and relationships in the ER model:


identifies a target entity (“user_articles” in this example, named after its Rails route) within the overall schema, and


identifies a relationship (“articles”) within the description of the source entity (“user”).

These URIs are globally unique, and even if they are never dereferenced by the client (i.e. the client isn’t interested in the ResourceTemplate metadata there – shame on them!), these values can be used by clients to select links reliably.


One of my sources of both good practice and inspiration has been Tim Berners-Lee’s Design Issues. I hadn’t got to the Metadata Architecture article until after drafting the above, and it’s quite gratifying to read it now.  Tim makes the ER comparison too, but rather than rehash that I will quote some of his key principles:

Metadata about one document can occur within the document, or within a separate document, or it may be transferred accompanying the document.

Link headers and link elements pointing to ResourceTemplate documents exemplify all three possibilities.

The URL space is an appropriate space for the definition of attribute names

Not only appropriate, but a lot more powerful than arbitrary tokens.  My initial plan to use simple names for rel and type attributes were definitely misguided.

As much as possible of the syntax and semantics should be able to be acquired by reference from a metadata document.

Within the scope of defining navigations around a web application I think this is achieved successfully.  A lot can be done by the client even without the metadata document, though clients will still need help in constructing URIs for members of collections.  I think there would a be place for the ResourceTemplate format (or something very much like it) even if there was a standard for URI Templates in link headers, but that’s a whole new article, perhaps my next one.

June 23, 2009

Link header dilemmas

Filed under: Web Integration — Tags: , , , , — Mike @ 12:07 pm

I received a very reasonable answer to my question (thank you Phil), but as a result of trawling through 12 months of archive came to realise that I’d misunderstood the guidance (or rather the relative lack thereof) regarding the use of the type link attribute.  The spec cites RFC 4287 The Atom Syndication Format and it appears that I have abused it by populating it with resource template names (sourced from Rails route names).  My fault entirely, lesson learned.

I’ll need to choose a more appropriate name for it therefore – either that or remove it altogether!  Possibilities:

  • name?  I quite like this, but is it too generic?
  • resourcename? Is this any better than name?
  • routename? Accurate when driven from Rails, but otherwise too implementation-specific I think
  • linkname? I quite link this too, but why not call the linkname on a link just name?
  • target?  Already taken, or at least too reminiscent of the target frame/window attribute.  Perhaps I’m on a better track here though? targettype maybe?

Aarrgghh!  We know that naming is hard; naming a name is doubly so.  I’ll sit on this for a while and hope that either inspiration or feedback comes…

June 19, 2009

Link headers, link elements, and REST

Filed under: Web Integration — Tags: , , , , , — Mike @ 3:14 pm

The house move looms (still no date yet, frustratingly) so I’ve had less time for programming (or blogging for that matter!).  I do however have an experimental and unreleased enhancement to the Ruby version of described_routes that generates link elements (for the <head> section of your html) or the yet-to-be-standardized link headers completely automatically.  Integrating them into your Rails application requires just a one-liner per layout or controller (or you can do all controllers in one go).  Drop me a line if you would like to play with it.

I have one question outstanding on the new spec (see it here on the ietf-http-wg list archives) but I have to say that I’m pretty happy with it all.  It takes the ideas of Partial template expansion in described_routes and adds a standardised way to relate content and metadata, making it even easier clients to navigate web applications without necessarily slurping up the entire site’s schema in one go.  It allows interaction to be 100% RESTful (HATEOAS and everything), and even if you prefer not to go that way as a client you can at least be confident that the application will be transparent, self-documenting and well-behaved.

May 13, 2009

Partial template expansion in described_routes

Filed under: Web Integration — Tags: , , , , — Mike @ 9:36 pm

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"
   "options":["GET", "POST"],
         "options":["GET", "PUT", "DELETE"],

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(
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!

May 12, 2009

Positional params for path-to/described_routes

Filed under: Web Integration — Tags: , , , , — Mike @ 2:59 pm

I’ve checked off another roadmap item for path-to, namely positional parameters.  This makes its metadata-driven client APIs feel even more natural.

Now, when indexing collection resources such as those seen in the Rails example, expressions such as

app.users['user_id' => 'dojo']

can be written


which is quite a lot neater!  This style can still be mixed with hash style parameters, such as

app.users['dojo', {'format' => 'json'}]


app.users['dojo']['format' => 'json']

In the Delicious API example,

delicious.posts['tag' => 'ruby']

can be written


The square brackets become round brackets in this case as the metadata doesn’t model posts as a collection – it’s just a resource that may take a number of optional parameters, the first of which is the tag name.  It took me a few moments to get used to this distinction, but I’m happy now that this gives a more obvious mapping to the underlying API.

May 3, 2009

Text format for described_routes

Filed under: Web Integration — Tags: , , — Mike @ 8:08 pm

described_routes is all about about metadata (e.g. for use by path_to),  but why not a more human-readable format?

So further to these instructions, the latest described_routes now has a plain text format.  It’s somewhat comparable to doing a rake routes, but brings out the structure of your application’s resources rather more clearly I think:

  $ rake described_routes:text
  (in /Users/asplake/railsapps/testapp)
  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}

If you install described_routes for runtime use, you get (1) full URI Templates (not just paths) and (2) the ability to pick out parts of the hierarchy:

  $ 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}


April 29, 2009

Nested path-to/described_routes and HTTParty

Filed under: Web Integration — Tags: , , , , , , — Mike @ 7:50 pm

I’ve finished the support for nested described_routes in path-to.  Example:

  require 'path-to/described_routes'
  app = PathTo::DescribedRoutes::Application.new(
      :json => Net::HTTP.get(URI.parse("http://example.com/described_routes.json")))

  app.users["user_id" => "dojo"].articles.recent
  #=> http://example.com/users/dojo/articles/recent
  app.users["user_id" => "dojo"].articles.recent.get
  #=> "<html>...</html>"

Or if you still prefer the named route:

  app.recent_user_articles("user_id" => "dojo").get   #=> "<html>...</html>"

And that’s it!  Is that a client-side API for free or what?  This just has to be better than hard-coded URIs (ugh).

I have also enhanced the Rails controller in described_routes so that you can get the JSON, YAML or XML for a given named route.  In this example we see HTTParty (path-to’s default HTTP client) parsing a JSON response (which I’ve reformatted just a little bit):

  app.described_route("route_name" => "admin_product", "format" => "json").get
  #=> {"name"=>"admin_product", "options"=>["GET", "PUT", "DELETE"],
          {"name"=>"edit_admin_product", "options"=>["GET"],
           "path_template"=>"/admin/products/{product_id}/edit{-prefix|.|format}", "rel"=>"edit",
           "optional_params"=>["format"], "params"=>["product_id"]
       "optional_params"=>["format"], "params"=>["product_id"]}

One last thing: as previously mentioned you’ll need grab the latest addressable from github – the published gem is a fix or two behind.

Older Posts »

Powered by WordPress