84
JSON AND THE ARGONAUTS API WYNNNETHERLAND

JSON and the APInauts

Embed Size (px)

DESCRIPTION

Guide to writing great API wrappers in Ruby.

Citation preview

Page 1: JSON and the APInauts

JSON AND THE ARGONAUTSAPI

WYNNNETHERLAND

Page 2: JSON and the APInauts

whoami

Page 4: JSON and the APInauts
Page 5: JSON and the APInauts

I write API wrappersA lot of API wrappers!

Page 6: JSON and the APInauts

& more!

Page 7: JSON and the APInauts

Why create API wrappers?

Page 8: JSON and the APInauts

After all, we have

Page 9: JSON and the APInauts

curl

Page 10: JSON and the APInauts

curl http://api.twitter.com/1/users/show.json?screen_name=pengwynn

Page 11: JSON and the APInauts

Net::HTTP

Page 12: JSON and the APInauts

url = "http://api.twitter.com/1/users/show.json?screen_name=pengwynn"Net::HTTP.get(URI.parse(url))

Page 13: JSON and the APInauts

Because we're Rubyists, and we want

Page 14: JSON and the APInauts

Idiomatic access

Page 15: JSON and the APInauts

{"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, ... }}

Page 16: JSON and the APInauts

{"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, ... }}

Page 17: JSON and the APInauts

Rubyified keys

Page 18: JSON and the APInauts

{"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, ... }}

Page 19: JSON and the APInauts

{"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, ... }}

Page 20: JSON and the APInauts

... and method names

Page 21: JSON and the APInauts

# 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.userend

Page 22: JSON and the APInauts

# 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.userend

More Ruby like than

Page 23: JSON and the APInauts

SYNTACTIC SUGAR

Page 24: JSON and the APInauts

Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()

Page 25: JSON and the APInauts

Twitter::Search.new.from('jnunemaker').to('pengwynn').hashed('ruby').fetch()

Method chaining

Page 26: JSON and the APInauts

stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores

Page 27: JSON and the APInauts

stores = client.stores({:area => ['76227', 50]}).products({:salePrice => {'$gt' => 3000}}).fetch.stores

Method chaining

Page 28: JSON and the APInauts

client.statuses.update.json! :status => 'this status is from grackle'

Page 29: JSON and the APInauts

client.statuses.update.json! :status => 'this status is from grackle'

Method chaining Bang for POST

Page 30: JSON and the APInauts

APPROACHES

Page 31: JSON and the APInauts

Simple Wrapping

Page 32: JSON and the APInauts

SOME TWITTER EXAMPLESTwitter Auth from @mbleighuser.twitter.post('/statuses/update.json', 'status' => 'Tweet, tweet!'

)

Grackle from @hayesdavisclient.statuses.update.json! :status => 'Tweet, tweet!'

Twitter from @jnunemaker client.update('Tweet, tweet!')

Wrapping

Wrapping... with style

Abstraction

Page 33: JSON and the APInauts

Why simply wrap?

Page 34: JSON and the APInauts

Insulate against change

Page 35: JSON and the APInauts

Leverage API documentation

Page 36: JSON and the APInauts

Why abstract?

Page 38: JSON and the APInauts

Simplify a complex API

Page 39: JSON and the APInauts

Provide a business domain

Page 40: JSON and the APInauts

TOOLS

Page 41: JSON and the APInauts

Transports

Page 42: JSON and the APInauts

Net::HTTP

Page 46: JSON and the APInauts

Parsers

Page 48: JSON and the APInauts

JSON

Page 50: JSON and the APInauts

multi_jsonhttp://github.com/intridea/multi_json

Page 51: JSON and the APInauts

Higher-level libraries

Page 53: JSON and the APInauts

HTTParty- Ruby module - GET, POST, PUT, DELETE - basic_auth, base_uri, default_params, etc.

- Net::HTTP for transport- Crack parses JSON and XML

Page 54: JSON and the APInauts

HTTPartyclass 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

...

Page 57: JSON and the APInauts

RestClient- Simple DSL- ActiveResource support- Built-in shell

RestClient.post(! 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' })

Page 59: JSON and the APInauts

Weary- Simple DSL- Specify required, optional params- Async support

Page 60: JSON and the APInauts

Wearydeclare "foo" do |r| r.url = "path/to/foo" r.via = :post r.requires = [:id, :bar] r.with = [:blah] r.authenticates = false r.follows = false r.headers = {'Accept' => 'text/html'}end

client.foo :id => 123, :bar => 'baz'

becomes

Page 62: JSON and the APInauts

RackClient- Rack API- Middleware!

client = Rack::Client.new('http://whoismyrepresentative.com')

Page 64: JSON and the APInauts

Faraday- Rack-like- Middleware!- Adapters

Page 65: JSON and the APInauts

Faradayurl = '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::Mashifyend

resp = conn.get do |req| req.url '/users/show.json', :screen_name => 'pengwynn'end

u = resp.bodyu.name# => "Wynn Netherland"

Page 67: JSON and the APInauts

Faraday Middleware- Hashie- Multi JSON- OAuth, OAuth2 as needed

Page 68: JSON and the APInauts

My current stack- Faraday- Faraday Middleware - Hashie - Multi JSON- OAuth, OAuth2 as needed

Page 69: JSON and the APInauts

Hashie- Mash- Dash- Trash- Clash

Page 71: JSON and the APInauts

HTTPScoop

Page 72: JSON and the APInauts

Charles Proxy

If you have an iOS app, you have an API ;-)

Page 73: JSON and the APInauts

Testing

Page 75: JSON and the APInauts

VCRhttp://github.com/myronmarston/vcr

Page 76: JSON and the APInauts

Artifice

Artifice.activate_with(rack_endpoint) do # make some requests using Net::HTTPend

a @wycats joint

http://github.com/wycats/artifice

Page 78: JSON and the APInauts

ShamRackShamRack.at("sinatra.xyz").sinatra do get "/hello/:subject" do "Hello, #{params[:subject]}" endend

open("http://sinatra.xyz/hello/stranger").read

#=> "Hello, stranger"

Page 79: JSON and the APInauts

ShamRackShamRack.at("rackup.xyz").rackup do use Some::Middleware use Some::Other::Middleware run MyApp.newend

Rack 'em up!

Page 80: JSON and the APInauts

Authentication

Page 81: JSON and the APInauts

Basic

Page 82: JSON and the APInauts

OAuthhttp://oauth.rubyforge.org/