Develop­ment

Create a Simple Client API Using method_missing

Create a Simple Client API Using method_missing

As developers interacting with APIs is a day to day occurrence. Thankfully things have begun to standardize around the RESTful pattern for most APIs. For most APIs a developer can usually find a client library for their language of choice. Here at WebVolta we work with Ruby and with it being a fairly popular language we can usually find libraries for most APIs that we use.

So what do you do when you can’t find one? Well working in Ruby there are many choices out there for us. We can go bare bones and craft our requests using the Net::HTTP libraries that are part of the core Ruby libraries. If that’s a bit too low level for you, there are libraries like HTTParty and Typhoeus. Lastly you can go really high level and use something like RestClient which abstracts a few more things.

What if you are prototyping though and all you really need is a quick and simple solution that abstracts just enough and doesn’t require you to code too much?

I ran across this exact scenario the other day and came up with a simple solution that leverages Ruby’s method_missing and uses HTTParty to crafts requests quickly while still being easy to understand.

Here’s my code:

class SimpleClient
  include HTTParty

  base_uri 'https://api.example.com/v1'

  attr_accessor :token

  def initialize(token = 'some_default_token')
    self.token = token
  end

  private

  def method_missing(endpoint, method = :get, **opts)
    path = [endpoint.to_s, opts.delete(:path)].compact.join(?/)
    response = self.class.send(method, "/#{path}", **opts, headers: headers)
    if response.nil? && response.code >= 300
      raise ArgumentError, "Request failed with code #{response.code}"
    else
      convert_to_openstruct(response.parsed_response || '{}')
    end
  end

  def convert_to_openstruct(response)
    if response['data'].present? && response['data'].is_a?(Array)
      response['data'] = response['data'].map { |row| RecursiveOpenStruct.new(row) }
    end
    RecursiveOpenStruct.new(parsed)
  end

  def headers
    {
      'Content-Type' => 'application/json',
      'Authorization' => "Token #{token}"
    }
  end
end

The code should be pretty easy to follow but I’ll quickly explain what’s going on.

For this API that I used this on I needed to be able to set a token in the header so I made it where you could define a default or pass one in to the initializer at line 8. What method_missing is doing is taking the method name and converting that into the resource type for the API (the first argument). The second argument is optional and is the HTTP verb for the request (ie. :get, :post, :put, :patch, :delete). And the last argument for method_missing slurps up the remaining parameters into an options hash.

The options hash are mostly options that HTTParty understands with the exception of the :path option which is used to build onto the URI for the request.

The reest of the code looks to make sure we had a successful request and if not it raises an error, otherwise, a RecursiveOpenStruct is built out of the resposnse and returned. For the specific use case I created this for all responses came wrapped in a data root node, which may or may not be the case for you.

Below you will find some examples of what using this client would look like.

  client = SimpleClient.new

  client.users(path: 1)
  #=> Sends a :get request to https://api.example.com/v1/users/1

  client.users(query: { filter: { client_id: 1 } })
  #=> Sends a :get request to https://api.example.com/v1/users?filter[client_id]=1

  client.products(:post, body: { name: 'Washing Detergent' }.to_json)
  #=> Sends a :post request to https://api.example.com/v1/products with a JSON body of '{ "name": "Washing Detergent" }'

  client.products(:put, path: 1, body: { name: 'Marbles' }.to_json)
  #=> Sends a :put request to https://api.example.com/v1/products/1 with a JSON body of '{ "name": "Marbles" }'

As you can see in a few lines of code we have created a client that is very adaptable and produces code that is very readable and easy to follow.

Feel free to extend the ideas here and contact us with any feedback you may have.

pin-icon phone