secture & code

How to Encapsulate an External API in a Ruby Gem

In Ruby environments, we are constantly installing and using third-party gems. When working with an external API it is also useful to use a Ruby gem. This way, maintaining it is outside the scope of our project and we can also reuse it in others (and share it 😉 ).

In this article we will explore how to use Gems and encapsulation, to make your next projects much easier.

Creating a gem from scratch

Install and use gems from others by Bundler is something basic when working with Ruby, but not all of us have made gems from scratch. In the following lines we will explain how to do it quickly.

To do this, on our computer with ruby and bundler installed, we will run the following command, making sure to write rspec when you ask us about the tests to be used:

bundle gem my_gem

This will generate a new folder my_gem in the directory where we are, with the basic structure of a gem included. The code will be placed in the lib/ folder and the tests in the spec/ folder. .gemspec includes the gem metadata and we should edit it to our liking.

How to use a Ruby Gem

We have two options: Compile and install the gem locally (whereupon we can require it if we open an irb console) or add it to the project where we want to use it. I will briefly explain both:

To compile and install the gem just execute these two commands in the root folder of the gem:

gem build my_gem.gemspec
gem install ./my_gem-X.Y.Z.gem

We must replace X.Y.Z by the version of our gem (declared in lib/my_gem/version.rb).

On the other hand, to include the gem in our project (and to be able to test it before publishing it) we will have to add it to the Gemfile of our project. We can do it this way:

gem 'my_gem', path: '/Users/user/Code/gems/my_gem'

Let us not forget the subsequent bundle install.

Encapsulating a ruby gem api

How to encapsulate an HTTP API

As a general rule, we will have access to a documentation of all the endpoints (or so I hope, for the sake of your mental health). It is completely normal that we are not going to use all of them, so we will take as an example an invented API whose endpoint is a POST /books to create a resource Book.

I am going to explain the idea and then I will leave code examples:

  • We will work on the gem Faraday, which allows a lot of flexibility when making HTTP calls.
  • We will create a class to encapsulate HTTP Requests.
  • We will create a class per resource to encapsulate its actions.

It will be more or less like this:

  • A Request class with a method do, which receives three parameters: method, path, options. Internally uses Faraday to manage the connection.
  • A Book class with a create method that receives name, author, which will call Request#do.
# lib/my_api/book.rb
module MyApi
  class Book
    def self.create(name:, author:)
      request.do(:post, '/books', { name: name, author: author })
    end

    private

    def request
      Request.new
    end
  end
end

# lib/my_api/request.rb
require 'json'

module MyApi
  class Request
    def do(method, path, opts={})
      serialized_opts = serialize_opts(method, opts)
      response = connection.send(method, path, serialized_opts)

      handle_response(response)
    end

    private

    def serialize_opts(method, opts)
      if %i[post put patch].include?(method)
        JSON.dump(opts)
      else
        opts
      end
    end

    def connection
      @connection ||= Faraday.new(
        url: ENV['MY_API_BASE_URL']
      ) do |with|
        with.adapter :net_http
      end
    end

    def handle_response(response)
      # TODO
    end
  end
end

Next steps

The next thing we could do is to map the possible errors that the API returns in case of our errors, and launch them in case the API returns these errors. For example, if a Book has no name, we could return a MyApi::Errors::Books::MissingNameError. Of course, we will also continue to map the endpoints that are useful to us, as well as manage the API responses.

Also making use of WebMock we can add tests to the HTTP requests that our gem will handle. This way we can make sure that the HTTP call correctly formats the parameters or the responses maintain a format that matches what we are expecting.

Encapsulating a ruby gem api

Wrap-up

We hope you found this little look at how to encapsulate APIs useful. If you're making an API, consider creating the gem yourself to make it easier for developers to communicate with your API without having to resort to handling HTTP calls. They'll thank you for it. 😜

Did you like this article? Keep learning about programming in this article of our blog and follow us on our networks to be updated when we have a new publication for you.

Picture of Pedro Adame

Pedro Adame

Ruby developer. 7 years in the mine and more to come (pun intended). Rock and Stone ⛏️
Picture of Pedro Adame

Pedro Adame

Ruby developer. 7 years in the mine and more to come (pun intended). Rock and Stone ⛏️

We are HIRING!

What Can We Do