:::: MENU ::::

Building an API in Ruby on Rails

I know there are quite a few tutorials and links out there on building an API for your Ruby on Rails application, but I figured that I would document a little bit about how it was done for Riding Resource, but at a high level.  A partner had requested access to some of our data via an API, and wanted the results to be spit out as XML, so here’s a little bit about how that was done.

First things first, it took me a little while to figure out exactly how to handle the whole XML thing, but it was actually far easier than I originally had thought.  Rails is smart enough to look for lots of different file types that match the action, so in the case of our API, all I had to do was make sure I had a .rxml (as opposed to .rhtml) file that matched the action.

Second, I wanted to restrict use of the API to authorized parties.  Just like how Google has API keys, I decided I wanted to do something similar.  Since I didn’t want to take the time to create some crazy key system that reverse-lookups the requestor and does stuff like that, I thought it might be a little easier to simply use the key to specify a legal set of requestor IP ranges/addresses.

Lastly, if the API request wasn’t from a legal requestor, I wanted to return nothing.

Here’s what I did (pseudocody) for the search controller

class ApiController < ApplicationController
  # api ip address ranges allowed
  API_KEYS = {"5b6ba960531c458021e8be98f3842c182c773b2f" => ['192.168.2.0/24', 'aaa.bbb.ccc.ddd'] }
 
  def search
    for ip in API_KEYS[params[:key]] do
      if IPAddr.new(ip).include?(IPAddr.new(request.remote_ip))
        @barns = Barn.find(:stuff)
        @count = @barns.length
        return
      end
    end
    render :action => :blank
  end

Some interesting things to note here. IPAddr is a nice ruby class that allows us to perform manipulations on IP addresses, one of which is checking if an address is included in a range. You have to require the ipaddr class in your environment in order to use it — it is not loaded by default.

for ip in API_KEYS[params[:key]] do
      if IPAddr.new(ip).include?(IPAddr.new(request.remote_ip))

Here we are iterating over all the ip ranges and IPs that are in the hash associated with the key provided by the incoming params. For each IP range/address, we check to see if the requesting IP is included in the range. Fortunately, IPAddr is smart enough to know that a single IP is included within itself, so using IP addresses as opposed to ranges is perfectly acceptable.

If the IP is within one of the ranges, we perform our find. If not, we render the blank action. The blank action has no code, and blank.rxml is empty as well. This way, if an illegal request comes in, we just do nothing — I don’t care to cater to people trying to access the API that shouldn’t be.

The XML part was tricky at first, but it actually turned out to be far simpler than I thought. Once you are in an XML view, Rails is kind enough to provide an xml object already for you, without needing to instantiate anything. This seemed to be contrary to a few tutorials I had found. Here’s some pseudocode to represent what i did:

xml.instruct! :xml, :version => "1.0"
xml.barns do |barn|
  xml.count @count
  xml.requestor request.remote_ip
  xml.api_key params[:key]
  xml.requested_location params[:zip]
  xml.requested_distance params[:dist]
  for b in @barns do
    xml.barn do
      xml.name b.name
      xml.address b.address
      xml.distance b.distance
      xml.phone b.phone
      xml.website b.website
      xml.url "http://www.ridingresource.com/contact/show/#{b.url}"
    end
  end
end

Because XML is a markup language that requires properly open and closed “containers” similar to HTML, you can see there are a lot of do/end blocks. The main do.end block is the barns block, which contains all of our results. I also was kind enough to let the API user know some of the things they asked of us, as well as the number of results we found.

xml.barns do |barn|
  for b in @barns do
    xml.barn do
      xml.something value
    end
  end
end

As we iterate over each result in @barns, we want to create an xml container for each one. The ease of Rails/Ruby here is awesome — you simply specify the container name, and then the value: xml.something value

The resultant XML output looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<barns>
  <count>10</count>
  <requestor>aaa.bbb.ccc.ddd</requestor>
  <api_key>key</api_key>
  <requested_location>30093</requested_location>
  <requested_distance>10</requested_distance>
  <barn>
    <name>Camp Creek Stables</name>
    <address>4150 Arcadia Industrial Circle SW  Lilburn GA, 30047</address>
    <distance>4.0317320843575</distance>
    <phone>7709252402</phone>
    <website></website>
    <url>http://www.ridingresource.com/contact/show/camp-creek-stables</url>
  </barn>

As you can see, it can be pretty easy to build an XML-returning API using Ruby on Rails. While this certainly is by no means a tutorial, it can provide some insight if you are a little stuck.

If you are interested in getting access to the Riding Resource API, please be sure to contact me at erik@ridingresource.com