Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

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.

class Api < Grape::API
  prefix 'api'
  rescue_from :all, :backtrace => true
  error_format :json
  include Api_v1
  include Api_v2
end

This API is routable in config/routes.rb.

match '/api/v1/\*other' => Api
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.

module Api_v1
  def self.included(api)
    api.version 'v1'
    api.include Api_v1_Me
    ...
  end
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.

module ApiModule
  module ClassMethods
    def module(mod)
      include mod
    end
  end
  def self.included(api)
    api.extend ClassMethods
  end
end

class Api < Grape::API
  include ApiModule
  prefix 'api'
  ...
end

The Api_v1 will use module instead of include.

module Api_v1
  def self.included(api)
    api.version 'v1'
    api.module Api_v1_Me
  end
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.

require 'spec_helper'

describe Api do
  describe "version" do
    it "includes version 1 and 2" do
      Api::versions.should == ['v1', 'v2']
    end
  end
end