This post originally appeared on Engine Yard.
It also appeard on the Quick Left Blog.
Introduction
In the modern web, API-based projects are becoming the norm. Why? For one thing, APIs are necessary to serve Single Page Applications, which are all the rage right now. From a business standpoint, APIs give companies a new way to charge others for access to their data. If you are part of a company that offers such a service, a great way to generate interest in your API is to offer a Ruby gem that makes fetching and consuming your data easy for Ruby developers.
Today, we’ll take a look at how to wrap an imaginary API in a new Ruby gem and share it with the world. If you want to follow along at home, you can clone the project from my GitHub account.
Our API
Let’s pretend we have an application called Ben’s Benzes that serves data about cars for sale. We’re exposing a RESTful API so that developers from other companies can serve our car data on their websites. For our first iteration, here are the routes we’ve set up:
Get info about a certain car currently for sale:
GET http://www.bensbenzes.com/api/v1/cars/active/:id
Get all the cars that are currently for sale:
GET http://www.bensbenzes.com/api/v1/cars/active
Setting Up the Gem
We’ll be using bundler, so begin by making sure you have that installed (you probably do). We’ll create the gem by running bundle gem <gem-name>
from the command line. I’m going to use benzinator
as the name of my gem. That name is now taken, so you’ll have to come up with your own. Open up the project directory and you should see:
1 2 3 4 5 6 7 8 9 |
|
Let’s open up benzinator.gemspec
and do a little configuration. Update the following lines with your name, email and a summary and description of the gem.
1 2 3 4 5 6 |
|
While we’re in here, let’s add the dependencies we’ll be using, right after bundler
and rake
. We’ll add testing tools as development dependencies, as well as hard dependencies on Faraday and json.
1 2 3 4 5 6 7 8 |
|
Writing a Test
We’ll need to make a test
folder, and put a test_helper.rb
into it. We’ll use the helper to pull in our gem and testing dependencies. We’ll also configure VCR and Webmock, which we’re using to stub out our server responses so that our gem isn’t dependent on access to the API for testing. See the VCR documentation for more about how this works.
1 2 3 4 5 6 7 8 9 10 |
|
Don’t forget to create the test/fixtures
folder so VCR has somewhere to put the fixtures. With that done, we’ll write the first test for our gem. Our goal is to create an object called Benzinator::Car that exposes #all
and #find
methods to wrap calls to our API. Let’s start by making sure that object exists:
1 2 3 4 5 6 7 8 |
|
Creating The Wrapper Model
If we now run ruby test/car/car_test.rb
, we get this error: uninitialized constant Benzinator::Car
. Looks like it’s time to write some code:
1 2 3 4 5 |
|
We’ll also need to require it in lib/benzinator.rb
:
1 2 3 |
|
Now if we run the test, it passes. Let’s write another one to make sure that our Benzinator::Car
model can give back the data for a car. Let’s imagine that an API call to http://www.bensbenzes.com/api/v1/cars/active/68
returns this JSON object:
1 2 3 4 5 6 7 8 9 |
|
We’ll want to make sure that our Benzinator::Car object has getter convenience methods all of the fields shown for each car. Given these API results, we could write a test like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Running this test, we get undefined method 'find' for Benzinator::Car:Class
. Let’s go define it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Now the test says we forgot to make it a Benzinator::Car model:
1 2 |
|
We can fix that, and make the attributes into getters at the same time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
That should take care of it. Now to test the #all
method.
Let’s imagine that a call to http://www.bensbenzes.com/api/v1/cars/active
responds with an array of 64 cars, with this Honda being the first one. We can rely on the API call giving back 64 cars today, but we hope there will be 6000 listed tomorrow. This is why we’re using VCR to save the result of the call as a fixture.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Running this, we get undefined method 'all' for Benzinator::Car:Class
. Let’s define it:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Sweet success! The tests pass!
Publishing and Using Our Gem
Now that our gem works, we can publish it to RubyGems. This is a pretty easy process. First, we’ll bump the version to 1.0:
1 2 3 4 |
|
Then we can bundle it up by running gem build benzinator.gemspec
. This will create a benzinator-0.1.0.gem
file in our project directory. To publish it , all we have to do is run gem push benzinator-0.1.0.gem
. You’ll be prompted for your RubyGems username and password, which you’ll need to create if you don’t have an account yet. After you enter your credentials, your gem is live!
Now anyone can use our gem in their Ruby projects. All they have to do is add it to their Gemfile with gem 'benzinator'
, and run bundle.
Conclusion
Winning! Now the whole wide world can access the Ben’s Benzes API from their Ruby project, with convenience methods to make things easier to work with.
For our next iteration, we could get a lot more in depth. We might want to add the ability to create, edit or destroy cars. If we decided to that, we might first build sort of authentication process into the gem. Once users have gotten a taste of our data and rely on it, our API might get so popular that we need to limit the number of API calls a user can make per day. We could then write subscription service to allow users greater access to your data at a cost.
One last note: you might want to use this technique to wrap someone else’s API, too! If you’re using a service that doesn’t offer a gem for its API, you can always write one and release it as open source!
Happy hacking!