How to Make Rails, Grape, and Her Work Together (With Caching!)

Tymon Tobolski

How to Make Rails, Grape, and Her Work Together (With Caching!)

If you follow our series on building APIs with Grape and Rails, you already know how to create powerful versioned APIs in a simple and straightforward way as well as improve Grape’s abilities with a number of useful tricks.

Building an interface, however, is often just the first step. The real challenge begins when your application needs to rely on data from several different APIs. It’s particularly difficult when you build applications using the Service Oriented Architecture pattern, which demands a scalable way for the software to communicate between various components.

If you want to know how to solve this problem, then this post is for you.

Dictionary

We’ll mainly use two gems: her and faraday.

Faraday is an HTTP client lib that provides a common interface over many adapters.

her, on the other hand, is an ORM that maps REST resources to Ruby objects, designed to build applications powered by a RESTful API instead of a database. Simply put, her makes REST resources behave like Active Record models:

class User
  include Her::Model
end

User.all
# GET "https://api.example.com/users" and return an array of User objects

@user = User.create(fullname: "Tobias Fünke")
# POST "https://api.example.com/users" with `fullname=Tobias+Fünke` and return the saved User object

Pretty cool, right? Now let’s get straight to the point.

Goal

Our objective is to make a Her-based wrapper with a configurable Faraday connection for a multi-host, token-authorized RESTful JSON API that supports HTTP headers cache. Easy-peasy, right? Right...? Well, it's not quite that simple.

Here at Monterail, however, we’ve written a number of Her-based wrappers and have found a nice, elastic pattern that we're eager to share with you.

Solution

First, let’s have a look at the model classes inside an imaginary monterail-api gem. The purpose of these classes is to map resources from other APIs to our own application.

Please note the uses_api method; it’s pretty important.

# lib/monterail-api/models.rb
module MonterailApi
  module V1
    class Hussar
      include Her::Model
      uses_api MonterailApi::V1.api    # Here

      attributes :name, :skills
    end

    class Wing
      include Her::Model
      uses_api MonterailApi::V1.api    # And here

      attributes :side
    end
  end
end

uses_api enables our models to talk to different APIs. It changes which API they will use to make their HTTP requests.

The only remaining task is to configure our wrapper in the client application:

# lib/monterail-api.rb
module MonterailApi
  module V1
    class ClientNotConfigured < Exception; end

    def self.configure(host, api_key, &block)
      @api = Her::API.new

      @api.setup :url => "http://#{host}/api/v1" do |c|
        # we love JSON, we really do
        c.use FaradayMiddleware::EncodeJson
        c.use Her::Middleware::AcceptJSON
        c.use Her::Middleware::FirstLevelParseJSON

        # simple header based authorizaiton
        c.authorization :token, api_key

        # allow for customizing faraday connection
        yield c if block_given?

        # inject default adapter unless in test mode
        c.adapter Faraday.default_adapter unless c.builder.handlers.include?(Faraday::Adapter::Test)
      end

      # This is very important. Due to way Her currently works
      # model files need to be required after configuring the API
      require "hussars/models"
    end

    def self.api
      # raise exception if somehow model classes gets required
      # before the API is configured
      raise ClientNotConfigured.new("Monterail") unless @api
      @api
    end
  end
end

Faraday and middleware

The great thing about this type of module is that it can be easily integrated with any app. This adaptability doesn’t mean that we can’t improve upon it, though. What about caching? We surely don’t want to slow down the backend by making too many useless HTTP requests, right?

Let’s take advantage of the faraday-http-cache and faraday_middleware gems for some ActiveSupport::Instrumentation integration.

# config/initializers/monterail_api.rb
MonterailApi::V1.configure("some.host.com", "secret") do |c|
  # let the magic happen - this will make use of ETag to skip unneed API calls
  c.use :http_cache, Rails.cache, :logger => Rails.logger

  # Active Support instrumentation
  c.use :instrumentation, :name => "external.monterail.v1"
end

Now we can simply use the code below to get the correct object without worrying about hitting the API too often:

MonterailApi::V1::Hussar.find(123) # It’s all cached!

That’s it!

This article on building her models and caching them is our third in a series on building APIs. We’ve previously published an introduction to using grape, a great gem that allows you to build APIs with tremendous ease. We expanded on that topic in the subsequent post by outlining some useful tips and tricks for improving grape. So what’s next? Well, we promised to talk more about data representers, so you’ll just have to wait and see!

We’re always keen to know what you think about the solutions that we provide here on Codetunes. Did you find this introduction to Grape useful? Has it changed the way that you think about APIs in Rails applications? (If so, we’re extremely happy, because that’s why we’re publishing this series.) 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.

Tymon Tobolski avatar
Tymon Tobolski