Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @ShopifyEng, @OpenSearchProj, ex-@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.