It's an HTTParty and Everyone Is Invited!

So I’ve made a boatload of gems that consume web services (twitter, lastfm, magnolia, delicious, google reader, google analytics). Each time I get a bit better at it and learn something new. I pretty much am only interested in consuming restful api’s and over time I’ve started to see a pattern. The other day I thought, wouldn’t it be nice if that pattern were wrapped up as a present for me (and others) to use? The answer is yes and it is named HTTParty.

The Pattern

Every web service related gem I’ve written makes requests and parses responses into ruby objects. So first let’s start with requests. The request methods that you make the most use of are get and post, with put and delete occasionally sliding in. I don’t know about you but I constantly forget how to use net/http. First, make the http, then the request. Is it get or post? How do I get the response body? Maybe I’m forgetful, but I always needed to have net/http’s rdoc open when working with it. Not anymore, though, cause now I just HTTParty like it’s 1999.

A Princely Example

To find something that I haven’t written a gem for, I hopped over to programmable web’s API directory. I chose Who is my representative‘s API because, frankly, I didn’t know it existed and it was really basic. So let’s say we want to get who my rep is:

require 'rubygems' require 'httparty' class Representative include HTTParty end puts Representative.get('http://whoismyrepresentative.com/whoismyrep.php?zip=46544').inspect # "<result n='1' ><rep name='Joe Donnelly' state='IN' district='2' phone='(202) 225-3915' office='1218 Longworth' link='http://donnelly.house.gov/' /></result>"

Yep, that is it. Include HTTParty and you are good to go. So, that was easy. Now we can make requests, but our response was just plain old xml. We want ruby objects! Let’s require rexml or install hpricot or libxml-ruby next, right? Wrong! Just set the format.

Automatically Parse XML

require 'rubygems' require 'httparty' class Representative include HTTParty format :xml end puts Representative.get('http://whoismyrepresentative.com/whoismyrep.php?zip=46544').inspect # {"result"=>{"n"=>"1", "rep"=>{"name"=>"Joe Donnelly", "district"=>"2", "office"=>"1218 Longworth", "phone"=>"(202) 225-3915", "link"=>"http://donnelly.house.gov/", "state"=>"IN"}}}

Yep, I’m not joking. That works, but let’s wrap things up a little bit and make a prettier API for the developer that will eventually use our Representative library.

require 'rubygems' require 'httparty' class Representative include HTTParty format :xml def self.find_by_zip(zip) get('http://whoismyrepresentative.com/whoismyrep.php', :query => {:zip => zip}) end end puts Representative.find_by_zip(46544).inspect # {"result"=>{"n"=>"1", "rep"=>{"name"=>"Joe Donnelly", "district"=>"2", "office"=>"1218 Longworth", "phone"=>"(202) 225-3915", "link"=>"http://donnelly.house.gov/", "state"=>"IN"}}}

There, that is a little better. One simple module include (HTTParty) and we can make requests and automatically get our xml responses parsed. Not to mention it’s so easy my mom could do it. What? Oh, you want to see JSON. Sure, no problem.

Automatically Parse JSON

require 'rubygems' require 'httparty' class Representative include HTTParty format :json def self.find_by_zip(zip) get('http://whoismyrepresentative.com/whoismyrep.php', :query => {:zip => zip, :output => 'json'}) end end puts Representative.find_by_zip(46544).inspect # {"results"=>[{"name"=>"Joe Donnelly", "district"=>"2", "office"=>"1218 Longworth", "phone"=>"(202) 225-3915", "link"=>"http://donnelly.house.gov/", "state"=>"IN"}]}

Holla! You thought you had me but you didn’t. Let’s make our example a little bit more complicated and add another method to get all the reps by name.

require 'rubygems' require 'httparty' class Representative include HTTParty format :json def self.find_by_zip(zip) get('http://whoismyrepresentative.com/whoismyrep.php', :query => {:zip => zip, :output => 'json'}) end def self.get_all_by_name(last_name) get('http://whoismyrepresentative.com/getall_reps_byname.php', :query => {:lastname => last_name, :output => 'json'}) end end puts Representative.get_all_by_name('Donnelly').inspect # {"results"=>[{"district"=>"2", "last"=>"Donnelly", "first"=>"Joe", "state"=>"IN", "party"=>"D"}]}

Notice any problems with that? I do. I’m repeating the domain and the output format in each request. Let’s fix that.

Helpers To DRY Things Up

require 'rubygems' require 'httparty' class Representative include HTTParty base_uri 'whoismyrepresentative.com' default_params :output => 'json' format :json def self.find_by_zip(zip) get('/whoismyrep.php', :query => {:zip => zip}) end def self.get_all_by_name(last_name) get('/getall_reps_byname.php', :query => {:lastname => last_name}) end end puts Representative.get_all_by_name('Donnelly').inspect # {"results"=>[{"district"=>"2", "last"=>"Donnelly", "first"=>"Joe", "state"=>"IN", "party"=>"D"}]}

I used base_uri to remove the duplication of the domain and default_params to automatically append :output => ‘json’ to each request. The previous examples give you a really good example of what HTTParty can do, but there is one last example I’ll show.

HTTP Authentication

The only thing we haven’t covered is authentication. API keys are simple, just add them to default_params I showed in the last example, but what about http authentication? Twitter uses http authentication, so our next example will use them.

require 'rubygems' require 'httparty' class Twitter include HTTParty base_uri 'twitter.com' basic_auth 'username', 'password' end puts Twitter.post('/statuses/update.json', :query => {:status => "It's an HTTParty and everyone is invited!"}).inspect # {"user"=>{"name"=>"Snitch Test", "url"=>nil, "id"=>808074, "description"=>nil, "protected"=>true, "screen_name"=>"snitch_test", "followers_count"=>1, "location"=>"Hollywood, CA", "profile_image_url"=>"http://static.twitter.com/images/default_profile_normal.png"}, "favorited"=>nil, "truncated"=>false, "text"=>"It's an HTTParty and everyone is invited!", "id"=>870885871, "in_reply_to_user_id"=>nil, "in_reply_to_status_id"=>nil, "source"=>"web", "created_at"=>"Mon Jul 28 20:07:52 +0000 2008"}

Cool. Wait, HTTP Authentication has to go in the class? No silly, Trix are for kids! The basic_auth method is just a class method so you can use it wherever class methods are acceptable. Try this on for size:

require 'rubygems' require 'httparty' class Twitter include HTTParty base_uri 'twitter.com' def initialize(user, pass) self.class.basic_auth user, pass end def post(text) self.class.post('/statuses/update.json', :query => {:status => text}) end end puts Twitter.new('username', 'password').post("It's an HTTParty and everyone is invited!").inspect # {"user"=>{"name"=>"Snitch Test", "url"=>nil, "id"=>808074, "description"=>nil, "protected"=>true, "screen_name"=>"snitch_test", "followers_count"=>1, "location"=>"Hollywood, CA", "profile_image_url"=>"http://static.twitter.com/images/default_profile_normal.png"}, "favorited"=>nil, "truncated"=>false, "text"=>"It's an HTTParty and everyone is invited!", "id"=>870885871, "in_reply_to_user_id"=>nil, "in_reply_to_status_id"=>nil, "source"=>"web", "created_at"=>"Mon Jul 28 20:07:52 +0000 2008"}

Miscellaneous

Install: sudo gem install httparty

Rubyforge: http://httparty.rubyforge.org

Github: http://github.com/jnunemaker/httparty

Lighthouse: http://jnunemaker.lighthouseapp.com/projects/14842-httparty/overview

Conclusion

Ok, so that was a really long introduction, but hopefully it was helpful. I’ve also included examples in the gem for those who want to venture more (twitter, delicious, amazon associates web services, most basic usage). Also, don’t be afraid of the code as it doesn’t have much (< 140 lines at the moment).