Building Structured API Clients with API Smith
Darcy Laycock is a web developer from Perth, Western Australia specialising in Ruby on Rails development. By day Darcy attends the University of Western Australia and works for The Frontier Group, a web development company. By night Darcy is hard at work building cool stuff. He’s also a humble 2011 Ruby Hero.
Whilst prototyping the early stages of a new app with Filter Squad, we found ourselves prototyping a lot of API clients for new versions of APIs that were lacking up to date clients or in the case of other APIs, were missing functionality we required.
As our starting tool set, we started with a fairly standard / typical API toolset - HTTParty for the HTTP portion and Hashie for the basic rapid prototyping data structures. As we prototyped more and more APIs, we started to see a clear pattern in how these two portions were interfacing and we started to see ourselves reinventing the same portions.
To this end, Steve Webb and I wrote a library called ‘API Smith’ - a simple tool set combining HTTParty and Hashie in a manner that we found was incredibly fast and powerful to prototype with. By combining several simple concepts together, API Smith aims to fulfill three basic requirements:
- Converting API responses into Ruby objects should be a straight forward and simple process.
- Extracting parts of a response shouldn’t be complex.
- HTTP is your friend and should be simple.
Based on these three things, today we’re going to briefly show how to build part of an API client for Clicky, a fairly robust analytics suite. For this cut down example, we’re going to model the tallies methods as noted in the Clicky API docs.
Before we do anything, you’ll want to first get api_smith. This is a simple matter of running:
gem install api_smith
Step 1. Declaring your data types
First, to get a better feel for the data we’re working with, we want to model the output response. From the Clicky docs, under the “Responses” section we see how the Clicky API will return the data to us In the API Smith side portion, we’re lucky because API Smith’s mapping objects - APISmith::Smash (for “Smarter Hash”) are fairly easy to map to the incoming data. In this example, we can simply subclass the API Smith class and then use the property
class method to declare what our possible fields are:
require 'rubygems'
require 'api_smith'
SMART_NUMBER_TRANSFORMER = lambda { |v| v =~ /^\d+$/ ? Integer(v) : v }
class TallyStat < APISmith::Smash
property :title
property :value, :transformer => SMART_NUMBER_TRANSFORMER
property :value_percent, :transformer => :to_f
property :url
property :clicky_url
# Goal Information
property :incompleted, :transformer => :to_i
property :conversion, :transformer => :to_f
property :revenue
property :cost
end
If you’ve worked with Hashie::Dash before, this will look reasonably familiar. The primary change that API Smith introduces that underpins most of the application design is that of ‘transformers’ - Namely, in this context a transformer is simply an object that responds to the call method and returns ‘transformed’ data.
In the example code above, you can see that we provide the :transformer
option on the property method. In the :value
field, we pass it a lambda (the basic form of a call-able object) and in the next three we pass it a symbol representing a method name. Under the hood, API Smith will convert the symbol in to a proc and will apply it only when a value is given for said property.
To test it out, fire up IRB, paste in the code and then run:
p TallyStat.new(:value => '1000', :clicky_url => 'http://example.com')
And you should see the output of our object. Likewise, as a short cut out APISmith::Smash subclasses also define call as a class method, making it possible to pass a subclass (e.g. Our TallyStat class) to another object as a smart transformer, e.g.:
property :tallies, :transformer => TallyStat
Along with this, APISmith::Smash is also smart enough to convert arrays and the like when called as a transformer.
Step 2. Writing the HTTP Client
The next step is to write a HTTP client by using the APISmith::Client
mixin. Much like the normal HTTParty
mixin, this gives us handy methods on the class like get
and post
, but in the case of API Smith we’re also given the option to configure endpoints (separate from the base_uri
), a fairly simple hierarchical way of declaring query and body parameters as well as specifying request options for HTTParty.
Secondly, it also adds tools to unpack and transform data. Similar to the :transformer
method on the property
class method for APISmith::Smash subclasses, it also has a :transform
option (With similar rules - Essentially, anything that has a #call method will used to take the raw response and convert it into a usable object.
In addition to this, it provides a :response_container
method that accepts an array of keys and will extract the data from the response before passing it to the transformer.
To get started, add the following below your code from the original code:
class ClickyClient include APISmith::Client
class TallyStatCollection < APISmith::Smash property :type property :date property :dates, :transformer => lambda { |c| c.map { |v| TallyStat.call(v[‘items’]) }.flatten } end
TALLY_METHODS = %w(visitors visitors-unique actions actions-average time-average time-average-pretty bounce-rate visitors-online feedburner-statistics)
class Error < StandardError; end
base_uri ‘http://api.getclicky.com/’ endpoint ‘api/stats/4’
attr_reader :site_id, :site_key
def initialize(site_id, site_key) @site_key = site_key @site_id = site_id add_query_options! :site_id => site_id, :sitekey => site_key end
TALLY_METHODS.each do |m| define_method m.tr(‘-‘, ‘_’) do |*args| api_tally_call m, *args end end
private
def check_response_errors(response)
if response.first.is_a?(Hash) and (error = response.first['error'])
raise Error.new(error)
end
end
def base_query_options
{:output => 'json'}
end
def api_tally_call(type, options = {})
get '/', :extra_query => {:type => type}.merge(options),
:response_container => [0], :transform => TallyStatCollection
end
end
This will declare a client that not only accepts a site id and key as parameters (e.g. client = ClickyClient.new(1234, 'your-site-key')
) but that will also:
- Correctly unpack errors (e.g. if an invalid api response is returned, it will raise a ClickyClent::Error exception).
- It will pass through any parameters in the query string
- It will automatically return a collection of tally stats, grouped by a date.
Whilst it’s a pretty basic example, if you try it out with some real credentials (hint: see the clicky api docks for an example site id and site key), you’ll see it works as expected:
c = ClickyClient.new('siteid', 'sitekey')
p c.visitors
Which should print out something similar to:
<#ClickyClient::TallyStatCollection dates=[<#TallyStat value=129>] type="visitors">
Conclusion
As you’ve seen in this rapid fire tutorial, we’ve managed to built a fairly simple client for a portion of the Clicky API. More importantly, even though it’s still reasonably brittle and missing support for many options in the Clicky API, in a relatively short time we were able to not only pull back data from API calls but transform them into structured objects we can further manipulate.
If you wish to take it further, there are a few more things you can implement to extend it into a more complete Clicky API client:
- Support for converting option types (e.g. date ranges)
- Better support for extracting data (e.g. getting daily data over a period of X days versus just the current day)
- Support for more API methods
- Actual proper tests
I’ve put a very rough version I wrote on GitHub here so you can take a look, but I suggest having a play personally - maybe there is a small app you use that has an API for which you could build a client using API Smith.
Share your thoughts with @engineyard on Twitter