API
JSON AND THE ARGONAUTS



                     WYNNNETHERLAND
whoami
+
A lot of API wrappers!

I write API wrappers
& more!
Why create API wrappers?
After all, we have
curl
curl http://api.twitter.com/1/users/show.json?screen_name=pengwynn
Net::HTTP
url = "http://api.twitter.com/1/users/show.json?screen_name=pengwynn"
Net::HTTP.get(URI.parse(url))
Because we're Rubyists, and we want
Idiomatic access
{"chart":{
    "issueDate":2006-03-04,
    "description":"Chart",
    "chartItems":{
      "firstPosition":1,
      "total...
{"chart":{
    "issueDate":2006-03-04,
    "description":"Chart",
    "chartItems":{
      "firstPosition":1,
      "total...
Rubyified keys
{"chart":{
    "issueDate":2006-03-04,
    "description":"Chart",
    "chartItems":{
      "firstPosition":1,
      "total...
{"chart":{
    "issue_date":2006-03-04,
    "description":"Chart",
    "chart_items":{
      "first_position":1,
      "to...
... and method names
# Retrieve the details about a user by email
#
# +email+ (Required)
# The email of the user to look within. To run getInfo...
# Retrieve the details about a user by email
#
# +email+ (Required)
# The email of the user to look within. To run getInfo...
SYNTACTIC SUGAR
Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
Method chaining


Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
Method chaining


stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
client.statuses.update.json! :status => 'this status is from grackle'
Method chaining        Bang for POST



client.statuses.update.json! :status => 'this status is from grackle'
APPROACHES
Simple Wrapping
SOME TWITTER EXAMPLES
Twitter Auth from @mbleigh
                                           Wrapping
user.twitter.post(
  ...
Why simply wrap?
Insulate against change
Leverage API documentation
Why abstract?
...or writing APIs to wrap APIs
Simplify a complex API
Provide a business domain
TOOLS
Transports
Net::HTTP
Patron
http://toland.github.com/patron/
Typhoeus
http://github.com/pauldix/typhoeus
em-http-request
http://github.com/igrigorik/em-http-request
Parsers
Crack
http://github.com/jnunemaker/crack

      Puts the party in HTTParty
JSON
yajl-ruby
http://github.com/brianmario/yajl-ruby
multi_json
http://github.com/intridea/multi_json
Higher-level libraries
HTTParty
http://github.com/jnunemaker/httparty


  When you HTTParty, you must party hard!
HTTParty
- Ruby module
  - GET, POST, PUT, DELETE
  - basic_auth, base_uri, default_params, etc.
- Net::HTTP for transport...
HTTParty
class Delicious
  include HTTParty
  base_uri 'https://api.del.icio.us/v1'
  
  def initialize(u, p)
    @auth = ...
monster_mash
http://github.com/dbalatero/monster_mash

   HTTParty for Typhoeus, a monster. Get it?
RestClient
http://github.com/adamwiggins/rest-client
RestClient
- Simple DSL
- ActiveResource support
- Built-in shell

RestClient.post(
! 'http://example.com/resource',
   :p...
Weary
http://github.com/mwunsch/weary
Weary
- Simple DSL
- Specify required, optional params
- Async support
Weary
declare "foo" do |r|
  r.url = "path/to/foo"
  r.via = :post
  r.requires = [:id, :bar]
  r.with = [:blah]          ...
RackClient
http://github.com/halorgium/rack-client
RackClient
- Rack API
- Middleware!

client = Rack::Client.new('http://
whoismyrepresentative.com')
Faraday
http://github.com/technoweenie/faraday
Faraday
- Rack-like
- Middleware!
- Adapters
Faraday
url = 'http://api.twitter.com/1'
conn = Faraday::Connection.new(:url => url ) do |builder|
  builder.adapter Farad...
Faraday Middleware
http://github.com/pengwynn/faraday-middleware
Faraday Middleware
- Hashie
- Multi JSON
- OAuth, OAuth2 as needed
My current stack
- Faraday
- Faraday Middleware
   - Hashie
   - Multi JSON
- OAuth, OAuth2 as needed
Hashie
- Mash
- Dash
- Trash
- Clash
HTTPScoop
If you have an iOS app, you have an API ;-)




         Charles Proxy
Testing
Fakeweb
http://github.com/chrisk/fakeweb
VCR
http://github.com/myronmarston/vcr
Artifice
    http://github.com/wycats/artifice

Artifice.activate_with(rack_endpoint) do
  # make some requests using Net:...
ShamRack
http://github.com/mdub/sham_rack
ShamRack
ShamRack.at("sinatra.xyz").sinatra do
  get "/hello/:subject" do
    "Hello, #{params[:subject]}"
  end
end

open...
ShamRack            Rack 'em up!
ShamRack.at("rackup.xyz").rackup do
  use Some::Middleware
  use Some::Other::Middleware
...
Authentication
Basic
OAuth
http://oauth.rubyforge.org/
OAuth2
http://github.com/intridea/oauth2
QUESTIONS?
 @pengwynn
JSON and the APInauts
JSON and the APInauts
Upcoming SlideShare
Loading in...5
×

JSON and the APInauts

9,096

Published on

Guide to writing great API wrappers in Ruby.

Published in: Technology

Transcript of "JSON and the APInauts"

  1. 1. API JSON AND THE ARGONAUTS WYNNNETHERLAND
  2. 2. whoami
  3. 3. +
  4. 4. A lot of API wrappers! I write API wrappers
  5. 5. & more!
  6. 6. Why create API wrappers?
  7. 7. After all, we have
  8. 8. curl
  9. 9. curl http://api.twitter.com/1/users/show.json?screen_name=pengwynn
  10. 10. Net::HTTP
  11. 11. url = "http://api.twitter.com/1/users/show.json?screen_name=pengwynn" Net::HTTP.get(URI.parse(url))
  12. 12. Because we're Rubyists, and we want
  13. 13. Idiomatic access
  14. 14. {"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
  15. 15. {"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
  16. 16. Rubyified keys
  17. 17. {"chart":{ "issueDate":2006-03-04, "description":"Chart", "chartItems":{ "firstPosition":1, "totalReturned":15, "totalRecords":25663, "chartItem":[{ "songName":"Lonely Runs Both Ways", "artistName":"Alison Krauss + Union Station", "peek":1, "catalogNo":"610525", "rank":1, "exrank":1, "weeksOn":65, "albumId":655684, ... }}
  18. 18. {"chart":{ "issue_date":2006-03-04, "description":"Chart", "chart_items":{ "first_position":1, "total_returned":15, "total_records":25663, "chart_item":[{ "song_name":"Lonely Runs Both Ways", "artist_name":"Alison Krauss + Union Station", "peek":1, "catalog_no":"610525", "rank":1, "exrank":1, "weeks_on":65, "album_id":655684, ... }}
  19. 19. ... and method names
  20. 20. # Retrieve the details about a user by email # # +email+ (Required) # The email of the user to look within. To run getInfoByEmail on multiple addresses, simply pass a comma-separated list of valid email addresses. # def self.info_by_email(email) email = email.join(',') if email.is_a?(Array) Mash.new(self.get('/', ! ! :query => { ! ! ! :method => 'user.getInfoByEmail', ! ! ! :email => email }.merge(Upcoming.default_options))).rsp.user end
  21. 21. # Retrieve the details about a user by email # # +email+ (Required) # The email of the user to look within. To run getInfoByEmail on multiple addresses, simply pass a comma-separated list of valid email addresses. # More Ruby like th an def self.info_by_email(email) email = email.join(',') if email.is_a?(Array) Mash.new(self.get('/', ! ! :query => { ! ! ! :method => 'user.getInfoByEmail', ! ! ! :email => email }.merge(Upcoming.default_options))).rsp.user end
  22. 22. SYNTACTIC SUGAR
  23. 23. Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
  24. 24. Method chaining Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()
  25. 25. stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
  26. 26. Method chaining stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores
  27. 27. client.statuses.update.json! :status => 'this status is from grackle'
  28. 28. Method chaining Bang for POST client.statuses.update.json! :status => 'this status is from grackle'
  29. 29. APPROACHES
  30. 30. Simple Wrapping
  31. 31. SOME TWITTER EXAMPLES Twitter Auth from @mbleigh Wrapping user.twitter.post( '/statuses/update.json', 'status' => 'Tweet, tweet!' ) Wrapping... with style Grackle from @hayesdavis client.statuses.update.json! :status => 'Tweet, tweet!' Abstraction Twitter from @jnunemaker client.update('Tweet, tweet!')
  32. 32. Why simply wrap?
  33. 33. Insulate against change
  34. 34. Leverage API documentation
  35. 35. Why abstract?
  36. 36. ...or writing APIs to wrap APIs
  37. 37. Simplify a complex API
  38. 38. Provide a business domain
  39. 39. TOOLS
  40. 40. Transports
  41. 41. Net::HTTP
  42. 42. Patron http://toland.github.com/patron/
  43. 43. Typhoeus http://github.com/pauldix/typhoeus
  44. 44. em-http-request http://github.com/igrigorik/em-http-request
  45. 45. Parsers
  46. 46. Crack http://github.com/jnunemaker/crack Puts the party in HTTParty
  47. 47. JSON
  48. 48. yajl-ruby http://github.com/brianmario/yajl-ruby
  49. 49. multi_json http://github.com/intridea/multi_json
  50. 50. Higher-level libraries
  51. 51. HTTParty http://github.com/jnunemaker/httparty When you HTTParty, you must party hard!
  52. 52. HTTParty - Ruby module - GET, POST, PUT, DELETE - basic_auth, base_uri, default_params, etc. - Net::HTTP for transport - Crack parses JSON and XML
  53. 53. HTTParty class Delicious   include HTTParty   base_uri 'https://api.del.icio.us/v1'      def initialize(u, p)     @auth = {:username => u, :password => p}   end ... def recent(options={}) options.merge!({:basic_auth => @auth})     self.class.get('/posts/recent', options) end ...
  54. 54. monster_mash http://github.com/dbalatero/monster_mash HTTParty for Typhoeus, a monster. Get it?
  55. 55. RestClient http://github.com/adamwiggins/rest-client
  56. 56. RestClient - Simple DSL - ActiveResource support - Built-in shell RestClient.post( ! 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' } )
  57. 57. Weary http://github.com/mwunsch/weary
  58. 58. Weary - Simple DSL - Specify required, optional params - Async support
  59. 59. Weary declare "foo" do |r| r.url = "path/to/foo" r.via = :post r.requires = [:id, :bar] r.with = [:blah] becomes r.authenticates = false r.follows = false r.headers = {'Accept' => 'text/html'} end client.foo :id => 123, :bar => 'baz'
  60. 60. RackClient http://github.com/halorgium/rack-client
  61. 61. RackClient - Rack API - Middleware! client = Rack::Client.new('http:// whoismyrepresentative.com')
  62. 62. Faraday http://github.com/technoweenie/faraday
  63. 63. Faraday - Rack-like - Middleware! - Adapters
  64. 64. Faraday url = 'http://api.twitter.com/1' conn = Faraday::Connection.new(:url => url ) do |builder| builder.adapter Faraday.default_adapter builder.use Faraday::Response::MultiJson builder.use Faraday::Response::Mashify end resp = conn.get do |req| req.url '/users/show.json', :screen_name => 'pengwynn' end u = resp.body u.name # => "Wynn Netherland"
  65. 65. Faraday Middleware http://github.com/pengwynn/faraday-middleware
  66. 66. Faraday Middleware - Hashie - Multi JSON - OAuth, OAuth2 as needed
  67. 67. My current stack - Faraday - Faraday Middleware - Hashie - Multi JSON - OAuth, OAuth2 as needed
  68. 68. Hashie - Mash - Dash - Trash - Clash
  69. 69. HTTPScoop
  70. 70. If you have an iOS app, you have an API ;-) Charles Proxy
  71. 71. Testing
  72. 72. Fakeweb http://github.com/chrisk/fakeweb
  73. 73. VCR http://github.com/myronmarston/vcr
  74. 74. Artifice http://github.com/wycats/artifice Artifice.activate_with(rack_endpoint) do # make some requests using Net::HTTP end a @wycats joint
  75. 75. ShamRack http://github.com/mdub/sham_rack
  76. 76. ShamRack ShamRack.at("sinatra.xyz").sinatra do get "/hello/:subject" do "Hello, #{params[:subject]}" end end open("http://sinatra.xyz/hello/ stranger").read #=> "Hello, stranger"
  77. 77. ShamRack Rack 'em up! ShamRack.at("rackup.xyz").rackup do use Some::Middleware use Some::Other::Middleware run MyApp.new end
  78. 78. Authentication
  79. 79. Basic
  80. 80. OAuth http://oauth.rubyforge.org/
  81. 81. OAuth2 http://github.com/intridea/oauth2
  82. 82. QUESTIONS? @pengwynn
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×