Skip to content

Instantly share code, notes, and snippets.

@steveklabnik
Created July 24, 2012 21:52
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save steveklabnik/3172911 to your computer and use it in GitHub Desktop.
Save steveklabnik/3172911 to your computer and use it in GitHub Desktop.
Hypermedia Proxy pattern in JSON
# this is based on an idea from Jon Moore.
# see https://gist.github.com/1478637 for his original XHTML version
require 'net/http'
require 'open-uri'
require 'json'
require 'pp'
# A simple get request should not require this much boilerplate.
def fetch_data(uri)
uri = URI(uri)
req = Net::HTTP::Get.new(uri.request_uri)
# we should really be using a json profile, but I didn't bother for this simple example.
req['Accept'] = "application/json"
res = Net::HTTP.start(uri.host, uri.port) {|http|
http.request(req)
}
JSON.parse(res.body)
end
class Status
attr_accessor :self_rel, :id
def initialize(opts={})
@id = opts['id']
@name = opts['name']
@self_rel = opts['links'].find{|link| link['rel'] == "self"}['href']
end
def name
@name || begin
fetch_data(self_rel)['name']
end
end
end
puts "getting partial representation"
# this will make 4 queries: one for the root, and one for each status
data = fetch_data("http://localhost:9292/partial")
statuses = data.collect {|datum| Status.new(datum) }
statuses.each do |status|
puts status.name
end
puts "getting full representation"
# this will make one query: one for the root
data = fetch_data("http://localhost:9292/full")
statuses = data.collect {|datum| Status.new(datum) }
statuses.each do |status|
puts status.name
end
require 'rubygems'
require 'bundler'
Bundler.require
require './hypermedia_proxy_pattern'
run HypermediaProxyPattern
source :rubygems
gem 'sinatra'
class HypermediaProxyPattern < Sinatra::Base
enable :inline_templates
get '/' do
erb :index
end
get '/full' do
content_type 'application/json'
erb :full
end
get '/partial' do
content_type 'application/json'
erb :partial
end
get '/status/:id' do
content_type 'application/json'
@id = params[:id]
erb :status
end
end
__END__
@@ index
Hello, world!
@@ full
[
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/1"
}
],
"id": 1,
"name": "name1"
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/2"
}
],
"id": 2,
"name": "name2"
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/3"
}
],
"id": 3,
"name": "name3"
}
]
@@ partial
[
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/1"
}
],
"id": 1
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/2"
}
],
"id": 2
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:9292/status/3"
}
],
"id": 3
}
]
@@ status
{
"links":[
{"rel":"self", "href":"http://<%= request.host %>:<%= request.port %>/status/3"}
],
"id":<%= @id %>,
"name":"name<%= @id %>"
}
@hannestyden
Copy link

The next.soundcloud.com backbone.js front end follows the same pattern.

@steveklabnik
Copy link
Author

Neat!

@canweriotnow
Copy link

A simple get request should not require this much boilerplate.

The net APIs have always felt kinds gross and Java-ish to me. Well, not as gross as Java, but... they could be so much more elegant. Which is why I always end up writing wrappers for Net::HTTP if I'm not using a gem that abstracts it.

@steveklabnik
Copy link
Author

Totally. I wanted to keep as few deps as possible.

@canweriotnow
Copy link

What, you don't want include HTTParty in ALL THE THINGS? 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment