Re-usability of code in Rails Grape API

Eugene Nikolaev
3 min readJul 13, 2020

--

Grape is a REST-like API framework for Ruby that can be integrated with frameworks like Rails or Sinatra, that provides rich DSL for creating JSON API.

One problem of using Grape, that I faced in my project, is that it’s basically not connected to Rails and doesn’t inherit from ActionController, so Ruby/Rails approach was not working well.

Most Rails applications have ApplicationController as root controller and controllers can inherit from each other sharing some logic, methods and filters such as before_action or after_action. But Grape controllers doesn’t share that behavior, all controllers should inherit from Grape::API. Because Grape acts like separate DSL some basic Ruby won’t work as well.

For example, if we declare method in our Grape controller and try to use it in endpoint:

module API
module V1
class MyController
< Grape::API
version 'v1', using: :path
format :json
prefix :api

def my_method
{ foo: :bar }
end

get
'/hello' do
my_method
end
end
end
end

It will give following error: NameError: undefined local variable or method `my_method’

To be able to use such methods Grape provides helpers functionality:

helpers do
def
my_method
{ foo: :bar }
end
end

This will work, and we can also put such methods in separate module and include to controller:

helpers ::API::V1::Helpers::MyHelpers

But what if we want to reuse some helpers for many controllers without explicitly including helper module in every controller? Let’s try inheritance and create new controller which will inherit as:

module API
module V1
class MyInheritedController
< MyController
version 'v1', using: :path
format :json
prefix :api

get '/hello_from_inherited' do
my_method
end
end
end
end

Still NameError: undefined local variable or method `my_method’ error.

So, looks like helpers can’t be reused across controllers using inheritance. Also, event hooks such as following, will not trigger, if defined in parent controllers:

before do
error!('404 Unauthorized', 404)
end

or

rescue_from ActiveRecord::RecordNotUnique do |e|
error!({ message: e.message }, 400)
end

So, the only workaround for this is to put that helpers and event hooks into base class, which mounts other controllers:

module API
module V1
class Base
< Grape::API
helpers ::API::V1::Helpers::MyHelper

rescue_from Exceptions::WrongRoleAccessAttempt do |e|
error!({ message: e.message }, 401)
end

before do
some_helper_method
end

mount API::V1::MyController
mount API::V1::MyController2
end
end
end

In this case we still can’t use helper methods in controllers unless we explicitly include them in each controller, but at least we can set some behavior which will be executed on every request. That will not help if we want to have some nested controllers structure, hooks can only be placed once.

So, from my experience working with Rails Grape I have figured some good and bad sides:

Good points:

  • Great for defining API endpoints and describing input and output formats in a very clear and easy to read way.
  • Using it allows to have auto generated documentation using tools like swagger, so no need to manually keep documentation updated.
  • Describing input and output data can be done in a very flexible way with many conditions, filters and validations. So, for describing API it has pretty much everything that can be needed.

Bad points:

  • Because, designed to be not just for Rails, but for plain Ruby as well, lacks any integration with Rails, ActionController features become completely unusable.
  • Being very special DSL, provides very certain way to deal with code, normal Ruby/Rails approach won’t work, because controllers do not behave as normal Ruby classes with methods.
  • Heavily lacks functionality for request related code re-usability, can’t properly use inheritance (which is basically the only way for re-usability in Ruby, even mixins are just inheritance at the end)

So, Grape API is great for defining and managing API interfaces, but if project requires advanced code manipulations, it might be not perfect solution, because by providing very specific DSL, it sacrifices certain extent of flexibility.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Eugene Nikolaev
Eugene Nikolaev

Written by Eugene Nikolaev

Senior Software Engineer at GMO GlobalSign

No responses yet

Write a response