Re-usability of code in Rails Grape API
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.