Table of Contents
In our first post of this series, we wrote about how to build a simple versioned API with Grape and Rails. If you didn't get a chance to read our introduction to this tool or haven't heard of it yet, Grape is a REST-based framework for building APIs in Ruby that was designed to run on Rack as well as to fit into existing Rails or Sinatra applications.
This second post will expand the topic even further and show you a few more tricks that we found to be very useful as we tinkered with the aforementioned tools. Grape truly is a great gem—even so, it still provided us with a few minor issues at the outset that we needed to work through. And while these issues were not exceedingly troublesome, they became more significant as the project progressed.
If you don't want to refactor your code later—or perhaps it's too late and you have do it anyhow—continue reading our solutions below. We promise it'll be worth your while. What we outlay in the following sections are real-life solutions that we implemented and tested in our own applications. No small talk. Only code.
Roar
Roar is a Ruby framework for parsing and rendering REST documents that lets you define your API document structure and semantics.
In other words, Roar is a simple way to manage the data that you want to be returned after an API request is made. Without it, you can either let Grape automatically convert all attributes of the model to JSON—and trust us, it's not something you want to do every time—or you can define the properties that will be returned in Grape on your own, which may soon turn out to be not-so-DRY.
We'll talk more about Roar in subsequent posts in this series, so let's stick to the basics this time. There are three common use cases that result from combining Roar's representers with Grape:
# Single entity
Hussar.first.extend(HussarRepresenter)
# Array
Hussar.all.map { |h| h.extend(HussarRepresenter) }
# A decorator of an object with additional arguments
WingsRepresenter.new(Wings.first, current_hussar)
It's not hard to see that as the API scope expands, you'll end up repeating yourself a lot. You also have to watch out for the edge cases; for example extending a nil
with a representer may randomly crash your application. This problem is why we wrote a Grape helper for representers. Just remember to put the code in the helpers block.
# app/controllers/api/v1/defaults.rb
def represent(*args)
opts = args.last.is_a?(Hash) ? args.pop : {}
with = opts[:with] || (raise ArgumentError.new(":with option is required"))
raise ArgumentError.new("nil can't be represented") unless args.first
if with.is_a?(Class)
with.new(*args)
elsif args.length > 1
raise ArgumentError.new("Can't represent using module with more than one argument")
else
args.first.extend(with)
end
end
def represent_each(collection, *args)
collection.map {|item| represent(item, *args) }
end
After adding our helper to the code, the first snippet that we showed above looks like this:
# Single entity
represent Hussar.first, with: HussarRepresenter
# Array
represent_each Hussar.all, with: HussarRepresenter
# A decorator of an object with additional arguments
represent Wings.first, current_hussar, with: WingsRepresenter
Isn't that a lot nicer? Also, if you set up your API the way we did in our previous post, you'll be able to use this helper in every namespace and module of your API. (And not a single repetition will be made.)
Pagination
Pagination is a must-have in virtually every application and there are many external gems which can handle it for you, such as will-paginate or Kaminari. We mostly use Kaminari as our pagination library of choice here at Monterail, but we discovered that, unfortunately, it doesn't function seamlessly with Grape.
But we're not the kind of people who sit and wait for somebody else to solve a problem, so we created a simple gem that integrates Grape and Kaminari so seamlessly that you won't have to worry about it anymore.
It's called grape-kaminari.
All you need to do is add it to your Gemfile, sit back, and watch the magic begin. The only thing that you need to do is to add two additional commands in your API endpoints.
desc 'Get paginated hussars'
# Annotate action with `paginate`.
# This will add two optional params: page and per_page
# You can optionally overwrite the default :per_page setting (10)
paginate
get do
hussars = Hussar.where(...)
represent_each paginate(hussars), with: HussarRepresenter
end
The result will be paginated and will also include pagination headers; these headers can then be used in external applications to indicate the overall number of records and pages, the current page, and the number of items per page:
X-Total: 42
X-Total-Pages: 5
X-Page: 3
X-Per-Page: 10
Pretty cool, right? We're quite pleased with the results.
Strong parameters
Rails 4 dropped support for attr_accessible and introduced strong parameters that provide an interface for protecting attributes from end-user assignment. The basic idea was to move the protection out of the model and into the controller where it belongs.
However, a relevant question emerges: is it possible to implement something similar in Grape? Broadly speaking, it's our controller
and should handle the protection from mass assignment, but can it?
The answer is yes.
All you need to do is implement this helper:
def permitted_params
@permitted_params ||= declared(params, include_missing: false)
end
And then use permitted_params
in the API methods instead of params
. The former redefines the parameters of the latter in a way which is adequate to the way permit
method works in Rails; it's really that simple.
Logger
Although they can be quite annoying, the issues we mentioned above shouldn't be treated as Grape's fixes but rather as slight improvements. This one, however, turns out to be crucial.
Grape only provides the most basic logging methods; furthermore, errors occuring in the API are not integrated with the Rails application logs in any way.
Fortunately, we devised a solution.
#app/controllers/api/base.rb
module API
class Dispatch < Grape::API
mount API::V1::Base
end
Base = Rack::Builder.new do
use API::Logger
run API::Dispatch
end
end
#app/controllers/api/logger.rb
class API::Logger
def initialize(app)
@app = app
end
def call(env)
payload = {
remote_addr: env['REMOTE_ADDR'],
request_method: env['REQUEST_METHOD'],
request_path: env['PATH_INFO'],
request_query: env['QUERY_STRING'],
x_organization: env['HTTP_X_ORGANIZATION']
}
ActiveSupport::Notifications.instrument "grape.request", payload do
@app.call(env).tap do |response|
payload[:params] = env["api.endpoint"].params.to_hash
payload[:params].delete("route_info")
payload[:params].delete("format")
payload[:response_status] = response[0]
end
end
end
end
You should now be able to debug your API just as easily as you can with the rest of the application.
Summary
This seems to be a natural place to wrap things up—we can't unveil all of our secrets at one time, can we? Don't worry though, we have some great insight about Grape coming down the pipeline. You can expect to read even more about this great tool in subsequent posts.
We promise that the next post in our Grape series will be even more intriguing and insightful. We'll be discussing the ins and outs of API caching and we're certain that you won't want to miss one word of it. And just as a reminder in case you've forgotten, this is the second tutorial in the Grape series, so make sure that you brief yourself on the introduction to building APIs with Grape.
We’re always keen to know what you think about the solutions that we provide here on Codetunes. We don't want to let any uncertainties go unanswered, given that it's been a while since our previous article was published. Do you have any other questions about Grape? Have you already implemented it in your application, or maybe you'll want to give it a try now that you have more information at your disposal? If not—why? Or maybe you’re convinced that you can figure out a better solution than ours? If so, prove it!
We’d love to hear your feedback.
Meet Ruby on Rails experts
We’ve been doing Ruby on Rails since 2010 and our services quality and technical expertise are confirmed by actual clients. Work with experts recognized as #1 Ruby on Rails company in the World in 2017 by Clutch.