Create a Simple Client API Using method_missing
By JD Guzman, May 16, 2017

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.