Doing an API Mashup with Sinatra
In the Ruby world, Sinatra is often seen as a lightweight alternative to Rails. Many confuse competition with the concept of the right tool for the right job. As I mentioned in a previous post, Sinatra is often the superior choice when building a simple application to grab some info from an API.
For the purposes of our example, we will build a small Sinatra application that will grab the current weather in Buffalo, NY—because that’s where I live.
To start, we ensure we are using Ruby 2.1 so we are stable. In my case, I’m using RVM so it is a simple case of:
$ rvm use 2.1
Using /Users/patrickhagerty/.rvm/gems/ruby-2.1.1
We have a directory for the application and we have our correct Ruby version. The next key is to find an API we can work with. For most applications that use an API, this is not too difficult. Weather, however, presents an unusual set of difficulties.
Setting It Up
More often than not, weather API services require payment. Not a big deal for our application as it is single use, but nonetheless, we don’t want to pay through the nostrils for something that just tells us the weather. Additionally, some weather APIs inundate us with information a meteorologist might enjoy, but we just want to know if an umbrella is necessary or if we need sunscreen instead.
To keep the focus on keeping it simple, I selected Open Weather Map, an open source weather API that provides current conditions. In order to build my API call, I first queried the city by name, then used the ID in order to get a better sort from the JSON that was returned.
http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial
This will give me the weather for Buffalo (id=5110629) in Imperial units (because, sadly, we don’t use metric in the US). With the API call situated, I can start to build my application. The first step is to create a Gemfile with the gems I need to make this happen so I can take advantage of Bundler for my gems and their dependencies.
source 'http://rubygems.org'
gem 'rubygems-update'
gem 'bundler'
gem 'sinatra'
gem 'rest-open-uri'
gem 'json'
I then head to the terminal and run bundle install
and it’s time to start coding! (NOTE: If you are unfamiliar with Bundler, you will notice a Gemfile.lock is created. This is the file that will manage the dependencies and necessary versioning of gems specified in your Gemfile. It should not be necessary to edit this file or to interact with it directly.)
We start by creating a file for the main thrust of our application. For this example, I’ll name the file weather.rb
and it will be where we put the main code for our application. We start with the standard setup of an index page for Sinatra. Note the require statements at the top of the file. In Sinatra, it is necessary to be specific about what gems are required to make the application function:
require 'rubygems'
require 'bundler'
Bundler.setup
require 'sinatra'
require 'json'
require 'rest-client'
get '/' do
end
Bringing It Together
From this point we will start adding the specific elements necessary to grab the information from the API. We begin by establishing where the API information will come from:
api_result = RestClient.get '[http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial](http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial)'
We then take the results we receive from the API call and parse them. As the results are in JSON, we use a JSON parsing call to get the information we need and assign them to a hash variable.
jhash = JSON.parse(api_result)
Here we will also initialize our output variable to ensure it is empty before we start rendering the information we are looking for.
output = ‘’
Now that we have our basic setup taken care of, we need to iterate through our hash and grab the information we are looking for. As we want the current weather, we are looking at the main
portion of the jhash we parsed out. To iterate over that information we do the following:
jhash['main'].each do |w|
title_tag = w[0]
info_item = w[1]
output << "<tr><td>#{title_tag}</td><td>#{info_item}</td></tr>"
end
Next we need to push our information to an erb file (which we’ll create later) so we can display our weather on the index page:
erb :index, :locals => {results: output}
This passes information to a file in our views folder called index.erb
. Any information in output can then be displayed on our index page, in our case, the current weather in Buffalo. Our final weather.rb
page looks like this:
require 'rubygems'
require 'bundler'
Bundler.setup
require 'sinatra'
require 'json'
require 'rest-client'
get '/' do
api_result = RestClient.get 'http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial'
jhash = JSON.parse(api_result)
output = ''
jhash['main'].each do |w|
title_tag = w[0]
info_item = w[1]
output << "<tr><td>#{title_tag}</td><td>#{info_item}</td></tr>"
end
erb :index, :locals => {results: output}
end
And our index.erb
, while not super pretty, will look like this:
<table>
<th>The Weather in Buffalo, NY, USA</th>
<tr>
<td><%= results %></td>
</tr>
</table>
<hr noshade>
Powered by the <a href = 'http://openweathermap.org/' target = _new>Open Weather Map API</a>
We then head to the terminal and run our application:
$ ruby weather.rb
[214-12-04 10:02:46] INFO WEBrick 1.3.1
[214-08-04 10:02:46] INFO ruby 2.1.1 (2014-02-24) [x86_64-darwin13.0] == Sinatra/1.4.5 has taken the stage on 4567 for development with backup from WEBrick
Conclusion
Focusing on keeping it simple, we have used Sinatra to grab an API and create a simple application. The benefit here is the lack of overhead a large scale framework might bring to the equation.
Take the time to explore other APIs and how you might use them. See how far you can take a simple Sinatra application to build dashboards or other information services for your team or for your own personal use.
P.S. Have you built an API before? Have you used Sinatra for a simple app like this? Share your experiences and thoughts. Throw us a comment below.
Share your thoughts with @engineyard on Twitter
OR
Talk about it on reddit