API and Object Interaction Extravaganza in Ruby

Part Two: From Execution to Integration

Chase
Analytics Vidhya

--

Continued from Part 1: From Sandbox to API Set Up

Part Two Sections

Step 5:

It is time to make some magic with our endpoint.

Photo by Nicolas Tissot on Unsplash

Before we do that, we should get some definitions out of the way (some new, some review). The following definitions are oversimplified for the purpose of understanding the rest of this post. Each of these terms could be the subject of their own blog post so do not feel as if you need to completely understand what they are right now (because I do not). You just need to trust that, in this context, each of these things do an important thing.

GET Request: an online request that our system sends to another systems that hosts information we are looking for.Parse: when you break something down into parts so they can be read and manipulated. E.g. Breaking a sentence down into words and those words into individual characters.Nested Data Structure: a sometimes maddening mix of hashes and arrays inside of each other. The JSON-parsed (see below) response to a GET request will be some form of nested data. Think of it as the coding equivalent of a Russian Nesting Doll.Web Resource: Anything that can be obtained from the web and uniquely identified. E.g. that one puppy photo you love.URI: Uniform Resource Identifier. A string of characters that identifies and names a web resource. Also, a Ruby Module with a library of methods and functions. When you see lowercase "uri", it refers to the web resource identifier and when you see uppercase "URI", it refers to the Ruby Module.URL: Uniform Resource Locater. A web address that is the location of a web resource on a network. Since it is a unique location, the URL for a resource is also technically its name.Net::HTTP : A built-in Ruby class that has a library of functions and methods that can be used to build user-agents like URI.Net::HTTPOK : Ruby Class and the type of object that is returned when a GET request is called with Net::HTTP. It has a method “body” that you will be using. Loves enthusiastically saying "OK" after regular Net::HTTP has done all the work.JSON: JavaScript Object Notation. A complex, yet human-readable, way of storing, transferring, and presenting the nested data from the GET response. Think of it as the drill sergeant of the GET Request response. JSON marches in and gets everyone in order.

Remember, the goal of all of this is to GET data that your program can break apart and manipulate. We could try to build out a custom translator method that sorts through the GET response or we can just rely on the glorious library of built-in methods that Ruby has already made for us. I think I will go with the latter.

Please note that the apiKey listed is not real so you will not be able to create a real GET request to Spoontacular unless you sign up for an account.

Set up your system to use URI and Net::HTTP

require ‘open-uri’require ‘net/http’

Save the the endpoint url to a variable "url"

url = “https://api.spoonacular.com/recipes/findByIngredients?apiKey=123abc123abc&ingredients=onion+pepper&number=10”

Call the parse method on the URI module and pass the variable “url” in. The result is saved to variable “uri." In other words, we parse the url and get back a uri.

uri = URI.parse(url)# => #<URI::HTTPS https://api.spoonacular.com/recipes/findByIngredients?apiKey=123abc123abc&ingredients=onion+cheese&number=10&ranking=2>

Create a GET request with Net::HTTP.get_response and pass in the variable "uri". The return value (or response) from this GET request is a Net::HTTPOK object that we will save to variable "response"

response = Net::HTTP.get_response(uri)#=> #<Net::HTTPOK 200 OK readbody=true>

We call the body method on “response” which retrieves the body text from the response. This is done because the body of the response has the data we want.

response_body = response.body#=>”[{\”id\”:514079,\”title\”:\”Easy Pinwheel Steaks with Spinach and Cream Cheese\”,\”image\”:\”https://spoonacular.com/recipeImages/514079-312x231.jpg\",\"imageType\":\"jpg\",\"usedIngredientCount\":1,\"missedIngredientCount\":2,\"missedIngredients\ . . . ”

Wow, what a mess. As you can see, at this point, response_body is a string of nested data mixed with all kinds of formatting notation. It ain’t pretty. This jumble of characters contains the data we want but in a way that is not accessible. Luckily, we have a way to clean the response up.

Here comes JSON to save the day.

require ’json’json_response = JSON.parse(response_body)#=> [
{“id”=>514079,
“title”=>”Easy Pinwheel Steaks with Spinach and Cream Cheese”,
“image”=>”https://spoonacular.com/recipeImages/514079-312x231.jpg",
“imageType”=>”jpg”,
“usedIngredientCount”=>1,
“missedIngredientCount”=>2,
“missedIngredients”=>
[{“id”=>11457,
“amount”=>4.0,
“unit”=>”oz”,
“unitLong”=>”ounces”,
“unitShort”=>”oz”,
“aisle”=>”Produce”,
“name”=>”baby spinach leaves”,
“original”=>”4 oz. fresh baby spinach leaves”,
“originalString”=>”4 oz. fresh baby spinach leaves”}]}]

Beautiful! Finally something we can work with. We have used a Ruby library designed to deal with JSON and called the library’s “parse” method to take the response_body and get rid of all of the formatting notation that was preventing us from accessing and understanding the data we requested.

Step 6:

Time to bring it all together.

The code above is great but it is hardcoded. If you plan to manually enter the the user’s selected ingredients in every GET Request, you may as well not make a program at all and just have the users text you directly for the recipe information. The idea is to bring automation to the process

To demonstrate how the code above can be built into your application, we will go through snippets from UIOLI

We will need a class dedicated to making GET requests when called. Remember OlderKid from the sandbox? This is what OlderKid became. However, it would not make sense to call this calls OlderKid so instead we will go with GetRequester. GetRequester will take in a url and run it through all of the code we just discussed.

require ‘net/http’require ‘open-uri’require ‘json’
class GetRequester def initialize(url) @url = url end
def get_response_body uri = URI.parse(@url) response = Net::HTTP.get_response(uri) response.body
end
def parse_json JSON.parse(self.get_response_body) endend

With the class defined this way, we only need to call the #parse_json method on the instance of GetRequester. This is because parse_json also calls the other method in the GetRequest class definition: get_response_body. In one method we can parse the url with uri, creates a GET request with Net::HTTP, call the body method on the response to that request, and finally have JSON parse the data into pure NDS delight.

recipe_url = “https://api.spoonacular.com/recipes/findByIngredients?apiKey=123abc123abc&ingredients=onion+pepper&number=10”recipes = GetRequester.new(recipe_url)

We set the code up. Now we need to connect this class with the user input functions of Use It Or Lose It.

The TTY-Prompt in our app (thank you Grant Yoshitsu) has just received a selection of ingredients from the user and saved it to an array.

uioli_array = ["onion", "pepper"]
Photo by Davor Nisevic on Unsplash

We need to convert the data that we got from the user into the form necessary for interpolation. This form is a string of food items separated by “+” per the API documentation. The end result we are looking for is “onion+pepper” so we will use the join method on the array and save it to variable “uioli_items.”

uioli_items = uioli_array.join(“+”)

Now we can use recipe_url and it will have the correctly formatted user inputed pasted in where the string interpolation field used to be.

recipe_url = “https://api.spoonacular.com/recipes/findByIngredients?apiKey=12abc12&ingredients=#{uioli_items}&number=10&ranking=2"

Great! Our url is ready to be passed into an instance of GetRequester. We will call parse_json on the instance and save it to variable “response”

recipe_url = “https://api.spoonacular.com/recipes/findByIngredients?apiKey=12abc12&ingredients=onion+pepper&number=10&ranking=2"response = GetRequester.new(recipe_url).parse_json

You may have noticed above, when we went over JSON parsing, that there is TONS of information about each of the 10 recipes that got returned from our GET. However, we only need two pieces of information from each recipe: name and id. For this reason, we will write a method called “clean_recipes” to clean up the nested data and only save the information we want to an array of hashes.

recipes = [{“name” => “Chicken Soup”,           “website_id” => “19283”},           {“name” => “Spaghetti and Meatballs”,           “website_id” => “39290”}]

This method will create an empty array, iterate through each recipe, save the name and Id to the hash as key/value pairs, shovel the hash into the array we created, and return the array.

def clean_recipes(recipes)     recipes =[]     
url.each do |recipe|
recipe_hash = {} recipe_hash[“name”] = recipe[“title”].titleize recipe_hash[“website_id”] = recipe[“id”] recipes << recipe_hash end recipes end

Finally, we will call the clean_recipes method and pass in recipes (the json-parsed response to the GET request). This will be saved to variable “results”.

results = clean_recipes(recipes)

TTY-Prompt takes over from here and presents the recipe names to the user for selection.

We have arrive at the end . . .

Yowza! That was a lot of information. My goal with this article was provide some perspective on and insight into APIs. Earlier, I said that APIs are exciting and I meant it. As a novice programmer, learning how to use API brought my applications to life. No longer are my applications doomed to a MacBook prison. They can talk to and interact with a whole universe of systems and the data they hold. The possibilities of what we can create are endless.

--

--

Chase
Analytics Vidhya

Software Engineer // Coding, Laughing, TV, Movies, Art, Music, Food, Drink & Boston Terrier Enthusiast