Delicious API with path-to/described_routes

The latest path-to contains an example (based on HTTParty’s) that queries Delicious via a Ruby API that’s 100% metadata driven.  Stripped of comments (the original commented version is here), the code is tiny:

require 'path-to/described_routes'
require 'pp'

config = YAML::load(File.read(File.join(ENV['HOME'], '.delicious')))

delicious = PathTo::DescribedRoutes::Application.new(
              :yaml => File.read(File.join(
                                     File.dirname(__FILE__),
                                     'delicious.yaml')),
              :http_options => {
                  :basic_auth => {
                      :username => config['username'],
                      :password => config['password']}})

pp delicious.posts['tag' => 'ruby'].get
pp delicious.posts['tag' => 'ruby'].recent['count' => '5'].get
delicious.recent_posts.get['posts']['post'].each do |post|
  puts post['href']
end

A couple of things to highlight are:

  1. The methods posts, recent, recent_posts are available thanks to the metadata – they’re not explicitly coded anywhere.  Similarly, URIs aren’t coded here either.
  2. The resource hiercharchy.  These are all separately identifiable things:
    • posts
    • posts['tag' => 'ruby']
    • posts['tag' => 'ruby'].recent
    • posts['tag' => 'ruby'].recent['count' => '5']

The metadata takes the form of described_routes-style resource templates, and I did it by hand this time in YAML:

---
- name: posts
  options:
  - GET
  uri_template: https://api.del.icio.us/v1/posts/get?{-join|&|tag,dt,url}
  optional_parameters:
  - tag
  - dt
  - url
  resource_templates:
  - name: recent_posts
    options:
    - GET
    uri_template: https://api.del.icio.us/v1/posts/recent?{-join|&|tag,count}
    rel: recent
    optional_parameters:
    - tag
    - count

Nested path-to/described_routes and HTTParty

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"],
       "resource_templates"=>[
          {"name"=>"edit_admin_product", "options"=>["GET"],
           "uri_template"=>"http://localhost:3000/admin/products/{product_id}/edit{-prefix|.|format}",
           "path_template"=>"/admin/products/{product_id}/edit{-prefix|.|format}", "rel"=>"edit",
           "optional_params"=>["format"], "params"=>["product_id"]
          }],
       "uri_template"=>"http://localhost:3000/admin/products/{product_id}{-prefix|.|format}",
       "path_template"=>"/admin/products/{product_id}{-prefix|.|format}",
       "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.