Fullscript Logo
Backend Development

Building a Fullscript API client ruby gem with method_missing

Author

Theo Ambie-Barango - Profile Picture
Theo Ambie-Barango

Date Published

Share this post

Fullscript, Stripe, Slack. What do these companies have in common? You guessed it, an API client ruby gem. But this wasn’t always the case for Fullscript. Let’s discover how we got there.

Over the 2021 Christmas holidays, I had a LOT of free time. So I figured, why not learn to create ruby gems? Sometimes when I look at the ruby gems ecosystem, it’s almost as if there really aren’t any more gems that can be created. Come to think about it, there even is a gem that can filter a text for profanity and swear words! So what could I possibly build to top that?

I had to remember the goal wasn’t to build the best gem, but to learn how to build gems. So I started with my bread and butter (butter mmm!). The Fullscript API.

Creating a ruby gem

To begin, what is a ruby gem? A ruby gem is a package that makes programming with ruby easier. If I’m building an application that prints out random numbers, for example, I would not want to do the actual randomization of numbers myself. That’s too much work, especially since someone has likely already thought about that problem. All I have to do is find a gem that does it for me and use that gem in my project. Bingo!

So, what if I wanted to create a ruby gem myself? Well, it isn’t as difficult as you’d think it is. I don’t want this article to focus on the generic process of building a ruby gem, so I will just link to the article I followed to create the framework for my fuelscropt-api-client gem.

The gem is called fuelscropt-api-client for now because this is a personal project and has not yet been officially approved by Fullscript to be listed on rubygems.org.

What is an API client

Now that we are experts in the creation and deployment of ruby gems, let’s get into this type of gem I’m looking to build — an API client. Basically, an API client is something that makes working with an API easier.

Currently, if an external developer, whose stack is in ruby, is trying to consume the Fullscript API, they would probably use a gem like HTTParty or Excon to construct their HTTP requests, making sure to pass in the necessary body and headers to the request, take care of refreshing the token, and then parse the response from our API.

What if they were able to just do something like this instead:

Example of how to use the api client

Wouldn’t that be amazing? Well, how do we do this?

Initializing the client

For the client to work properly, the external developer has to provide it with their application’sclient_id, secret, redirect_uri and what server they want to work in. This will enable the client to make requests to the proper endpoint as well as refresh the token if it gets stale.

So in our Fullscript class of the gem, we create a config method that all it does is yield self so that these credentials can be set using their public attr_accessors.

Here’s what this config method looks like:

config class method

This is a common trick with most ruby gems.

Obtaining a token

To be able to get the client working, we need a valid token to talk to the Fullscript API. The API client relies on the external developer to initialize the OAuth dance up until they’ve received an authorization code, which they can then grant to the client to exchange for an OAuth token like so:

1client.create_an_oauth_token(grant)

Alternatively, if they already have architecture set-up for receiving the authorization code and exchanging it for a grant, then we can have them provide the token to the client as a hash:

1client.use_token(
2 access_token: “access_token”,
3 expires_in: 7200,
4 refresh_token: “refresh_token”,
5 scope: “catalog:read”,
6 created_at:202106–16T14:57:21.000Z”,
7)

The client can then use this token and refresh it at will.

Method Missing

Now, we move on to the main part of the gem. How do we program all the endpoints that our API supports, and potentially new ones that will be added?

The simple answer is, we don’t! Shut the front door! So what makes the gem work then?

Great question. We leverage two things:

  1. Ruby’s method_missing methodmethod_missing: When you send a method to an object that the object is not able to respond to, ruby invokes the method_missing method to then raise this familiar error we’ve probably all seen at least once: NoMethodError (undefined method <insert method> for <insert Object>)for. We then overwrite that method in our gem and do something interesting. This idea is thanks to a rubyConf talk that intrigued me.
  2. Openapi JSON parsing: In a previous project cycle, the API team at Fullscript had implemented an openapi schema for our API. This generates a JSON object with all the API’s endpoints, urls, required parameters etc.

So, how do we combine these 2 tools to create the api client?

When the gem receives a request like client.list_all_patients, obviously since we don’t have that method explicitly defined, we go into the method_missing method. In this method, we’ll then read through the openapi JSON to find the “List all patients” object.

Once we find this object, we will fetch out the following important information:

  1. HTTP method: What HTTP method is needed for this request. GET, PATCH, POST?
  2. Path: What URL do we send this request to?
  3. URL params: Does the URL accept any params?
  4. Body: Should we pass along a body with the request?

Once all that is done, we send it all off to Excon.

Needless to say, if that api method does not exist (meaning that we do not have an endpoint for that request) we will invoke super to raise that familiar NoMethodError error.

So here is what the #method_missing#method_missing overwrite looks like:

method_missing overwrite logic

Next steps

Where do we take this? The gem still has some work to be done on it before it’s “shippable”. Some of this work includes:

  1. Proper versioning
  2. Better and improved logic for refreshing tokens
  3. Stabilizing the openapi JSON schema so the gem isn’t fragile
  4. Caching or defining methods “on the fly” to prevent reading the JSON schema for every request.
  5. Getting an external developer whose stack is in ruby to test the gem out
  6. Code cleanup

In its ideal state, and with an interested external developer, this gem can be super useful in speeding up their build process. Currently, it is also useful as a testing tool for the API.

Conclusion

Thank you for reading this, and I hope you found it intriguing. If you’d like to view the source code of this gem, it’s available in this public repo.

Share this post