Cómo encapsular una API externa en una Gema de Ruby

En entornos de trabajo de Ruby, estamos constantemente instalando y usando gemas de terceros. Al trabajar con una API externa también es útil utilizar una gema de Ruby. De esta forma, mantenerla queda fuera del alcance de nuestro proyecto y además podemos reutilizarla en otros (y compartirla 😉).

En este artículo exploraremos como usar Gemas y a encapsular, para que vuestros próximos proyectos sean mucho mas sencillos.

Creando una gema desde cero

Instalar y usar gemas de otros mediante Bundler es algo básico al trabajar con Ruby, pero no todos hemos hecho gemas desde cero. En las siguientes líneas explicaremos cómo hacerlo de forma rápida.

Para ello, en nuestro equipo con ruby y bundler instalados, ejecutaremos el siguiente comando, asegurándonos de escribir rspec cuando nos pregunte sobre los tests a utilizar:

bundle gem my_gem

Esto generará una nueva carpeta my_gem en el directorio donde nos encontremos, con la estructura básica de una gema incluida. El código lo meteremos en la carpeta lib/ y los tests en la carpeta spec/. Por último, el archivo .gemspec incluye los metadatos de la gema y deberíamos editarlo para ponerlo a nuestro gusto.

Cómo usar una Gema de Ruby

Tenemos dos opciones: Compilar e instalar la gema localmente (con lo cual podremos requerirla si abrimos una consola de irb) o añadirla al proyecto donde queramos usarla. Explicaré brevemente ambas:

Para compilar e instalar la gema basta con ejecutar estos dos comandos en la carpeta raíz de la gema:

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

Deberemos sustituir X.Y.Z por la versión de nuestra gema (declarada en lib/my_gem/version.rb).

Por otra parte, incluir la gema en nuestro proyecto (y poder probarla antes de publicarla) deberemos añadirla al Gemfile de nuestro proyecto. Podemos hacerlo de esta forma:

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

No olvidemos el posterior bundle install.

Encapsular una api gema de ruby

Cómo encapsular una API HTTP

Por norma general, tendremos acceso a una documentación de todos los endpoints (o eso espero, por el bien de tu salud mental). Es completamente normal que no vayamos a utilizarlos todos, así que tomaremos como ejemplo una API inventada cuyo endpoint sea un POST /books para crear un recurso Book.

Voy a explicar por encima la idea y luego dejaré ejemplos de código:

  • Trabajaremos sobre la gema Faraday, que permite mucha flexibilidad a la hora de hacer llamadas HTTP.
  • Crearemos una clase para encapsular las Request HTTP.
  • Crearemos una clase por recurso para encapsular sus acciones.

La cosa quedará más o menos así:

  • Una clase Request con un método do, que recibe tres parámetros: method, path, options. Internamente usa Faraday para gestionar la conexión.
  • Una clase Book con un método create que recibe name, author, el cual llamará a Request#do.
# lib/mi_api/book.rb
module MiApi
  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/mi_api/request.rb
require 'json'

module MiApi
  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['MI_API_BASE_URL']
      ) do |conn|
        conn.adapter :net_http
      end
    end

    def handle_response(response)
      # TODO
    end
  end
end

Próximos pasos

Los siguiente que podríamos hacer es mapear los posibles errores que devuelva la API ante errores nuestros, y lanzarlos en caso de que la API nos devuelva estos errores. Por ejemplo, si un Book no tiene name, podríamos devolver un MiApi::Errors::Books::MissingNameError. Por supuesto, también continuar mapeando los endpoints que nos sean útiles, así como gestionar las respuestas de la API.

También haciendo uso de WebMock podemos añadir tests a las requests HTTP que vaya a gestionar nuestra gema. De esta manera podemos asegurarnos de que la llamada HTTP formatea correctamente los parámetros o las respuestas mantienen un formato que coincida con el que estamos esperando.

Encapsular una api gema de ruby

Wrap-up

Esperamos que te haya resultado útil este pequeño vistazo a cómo encapsular APIs. Si estás haciendo una API, considera crear tú mismo la gema para facilitar a los desarrolladores comunicarse con tu API sin tener que recurrir a manejar llamadas HTTP. Te lo agradecerán. 😜

¿Te ha gustado este artículo? Sigue aprendiendo sobre programación en este artículo de nuestro blog y síguenos en nuestras redes para estar al día de cuando tengamos una nueva publicación para ti.

Picture of Pedro Adame

Pedro Adame

Picture of Pedro Adame

Pedro Adame

We are HIRING!

What Can We Do