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