Daniel Doubrovkine bio photo

Daniel Doubrovkine

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

Email Twitter LinkedIn Github Strava
Creative Commons License

A lot of people ask me whether we use Rails controllers for our API. We don’t, we use Grape. Grape is a Rack-based system and a DSL that provides a cleaner separation, some API-specific functionality and generally a better syntax. Now that we have dealt with exceptions and authentication we realized that the amount of functionality exposed in the API has grown exponentially in one single Ruby file. Let’s refactor it into modules.

Here’s our current code from API v1.

class Api_v1 < Grape::API
    prefix 'api'
    version 'v1'
    rescue_from :all, :backtrace => true
    error_format :json
    helpers do
        def authenticated
            if warden.authenticated?
                return true
            else
                error!('401 Unauthorized', 401)
            end
        end
    end
    namespace :me do
        # GET /api/v1/me/info
        get "info" do
            authenticated
            current_user.as_json({properties: :self})
        end
    end
end

We want a separate file for helpers and for the me API. We can move the helpers into a module and include it normally.

module ApiAuth
  def authenticated
    ...
  end
end

The namespace DSL is a bit tricky. Those namespace and get are actually namespace functions in Grape::API. Fortunately Ruby calls included(module) for every included module. We can call the public methods on a namespace function ourselves.

module Api_v1_Me
  def self.included(api)
    api.namespace :me do
      # GET /api/v1/me/info
      get "info" do
        ...
      end
    end
  end
end

Let’s combine all of this into an API class.

class Api_v1 < Grape::API
  prefix 'api'
  version 'v1'
  rescue_from :all, :backtrace => true
  error_format :json
  helpers do
    include ApiAuth
  end
  include Api_v1_Me
end

The nice thing about this implementation is that we can now compose an API v2 with a bunch of v1 modules and some v2 ones. The not-so-nice part is the included construct. I’d like to write the following.

module Api_v1_Me
    include Grape::APIModule
    namespace :me do
        # GET /api/v1/me/info
        get "info" do
        ...
        end
    end
end

Beer to anyone who can implement this, I tried for hours, in vain - I think Grape will need some major refactoring to support modules this way. Fork Grape on Github.