Modularizing a RoR Grape API: Multiple Versions

Back | grape, rails, ruby | 7/30/2011 |

We made some progress with modularizing our Grape API in the last post. But we only had one version and declared Api_v1 as our main API entry. Unless you’re Netflix and need an API per device family (I know, wow!, 18’000 different devices use the Netflix API), you might want to make a Grape API with two versions.

We’ll start by declaring an API class the way we would like to see it.

  1. class Api < Grape::API
  2.   prefix 'api'
  3.   rescue_from :all, :backtrace => true
  4.   error_format :json
  5.   include Api_v1  
  6.   include Api_v2
  7. end

This API is routable in config/routes.rb.

  1. match '/api/v1/*other' => Api
  2. match '/api/v2/*other' => Api

What does Api_v1 or Api_v2 look like? It’s  a little tricky. We need to include api modules into the parent Grape API, like this.

  1. module Api_v1
  2.   def self.included(api)
  3.     api.version 'v1'
  4.     api.include Api_v1_Me
  5.     ...
  6.   end
  7. end

Unfortunately Module::include is private. Let’s expose it as module by extending the Api class with the methods of ApiModule::ClassMethods. I personally find this included / extend pair particularly elegant.

  1. module ApiModule
  2.   module ClassMethods
  3.     def module(mod)
  4.       include mod
  5.     end
  6.   end
  7.   def self.included(api)
  8.     api.extend ClassMethods
  9.   end
  10. end

  1. class Api < Grape::API
  2.   include ApiModule
  3.   prefix 'api'
  4.   ...
  5. end

The Api_v1 will use module instead of include.

  1. module Api_v1
  2.   def self.included(api)
  3.     api.version 'v1'
  4.     api.module Api_v1_Me
  5.   end
  6. end

Don’t forget to write some tests. I’ve made a pull request into Grape exposing API versions and routes, so I can actually write a test now that makes sure we have both versions of the API properly loaded. This goes into spec/requests/api_spec.rb.

  1. require 'spec_helper'
  2.  
  3. describe Api do
  4.   describe "version" do
  5.     it "includes version 1 and 2" do
  6.       Api::versions.should == [ 'v1', 'v2' ]
  7.     end
  8.   end
  9. end