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