Run — Refactor — Break. Repeat.
Write a program, make it run. Yay! It works! Tweak it *holds breath*…and it breaks. Spend hours trying to make it work again. This very quickly became my coding ritual while creating my first CLI (command-line interface) application to access external data utilizing an API (application programming interface). You may view a walkthrough of my project here as I go into greater detail about the process itself below.
The “making it run” part.
For several minutes I sat looking at the skeleton of my project. Hurray! I git cloned a repository that came from my very own Git Hub account. Once the initial triumph wore off, the overwhelming sense of “So, where do I even start?” crept in. My vacant VS code looked back at me, with its prepped files labeled ‘api.rb’, ‘cli.rb’ and ‘something.rb’ waiting expectantly in the wings. Fortunately, we have an abundance of resources available to give us a nice little push as we teeter-totter onward with our coding training wheels. I reviewed our lessons and scoped a couple of sample projects.
I decided I couldn’t get anywhere without data, so I’d tackle my API url first. I wanted users to be able to input three different ingredients, and get back recipes containing those ingredients in return. The inspiration was having very little time as a student, and equally limited groceries at home. So, I wanted an application to do the searching for me, after telling it what I had available in my pantry and fridge. With some playing around on the Edamam API website I discovered how the API url looked when it contained three ingredients. After some consideration, it seemed the best way to validate the user’s ingredients, and return the search results if so, would be to have my API take in an initial argument of ingredient_input
— a sorted array of ingredients the user inputs — and interpolate the elements of this array directly into the url. This way, I would simply have to check that the url returned something. If it returned an empty hash or nil
then the ingredients were invalid (e.g. not food items, or spelled incorrectly).
class Apidef initialize(ingredient_input)@ingredient_input = ingredient_inputurl = "https://api.edamam.com/search?q=#. {ingredient_input[0]}%2C+#{ingredient_input[1]}%2C+#{ingredient_input[2]}&app_id=#{ENV["API_ID"]}&app_key=#{ENV["API_KEY"]}"
uri = URI(url)
response = Net::HTTP.get(uri)
recipes = JSON.parse(response)
@recipe_hits = recipes["hits"][1..5]
end
Well, that’s groovy. But what does initializing a new Api
object create? The first part of the #initialize
method sets the variable url
equal to the API url. Next, the built-in Ruby class Net::HTTP
gets the data and the built-in JSON
module has a #parse
method that converts the data into a much more agreeable format for Ruby: a hash or array.
Okay, that’s groovy and all. But when I instantiate a new Api
object — what does that create exactly? *Enter our friend Pry
*
Pry
is a gem that allows, as the name suggests, you to pry into your code and stop it mid-method, wherever you have placed a binding.pry
. Now, Pry
and I are still getting to know each other, she can be a finicky friend, but we certainly learned a lot about each other during this project.
Oh, the thrill when I hit binding.pry
in my #fetch_api
method and typed api
to see what exactly Api.new(ingredient_input)
had created.
class Cli def fetch_api
api = Api.new(ingredient_input)
binding.pry
api.create_recipe
end
end
It was the most beautiful sight — an array called “hits” that contained nested hashes (and more arrays). Inside the hit
array, the recipe
key’s value equalled one-hundred hashes, each a different recipe. I decided that one-hundred was a little much, which is why I gave my instance variable @recipe_hits
a range of five. Thanks to pry
I was about to discern how to navigate within the recipe
key. Now I had all of the information I needed to create my Recipe
objects! In the Cli
class, I wrote an instance method #create_recipe
to iterate through each instance of an Api
object which contains five recipe hashes generated from the three ingredients initially inputted by the user. Each iteration instantiates a new Recipe
object, with arguments that correlate to a key-value pair in the “recipe” hash. My API contained a LOT of information, from dietary concerns to very detailed nutritional content. To simplify things for the user, Recipe
objects would be instantiated with five arguments. (I created a fifth argument, ingredient_input
, to have the option to add more functionality later that allowed the user to review previous searches and search results.)
def create_recipe
@recipe_hits.each { |hits| Recipe.new(hits[“recipe”][“label”], hits[“recipe”][“ingredientLines”], hits[“recipe”][“source”], hits[“recipe”][“url”], self.ingredient_input) }
end
So now my application worked! It correctly fetched data, and generated new objects. The “easy” part was formatting this data so that it was pleasant and legible for the user. Thankfully, we’ve had a lot of practicing using each_with_index
and other devices to sort through hashes and arrays. The final details of my project were spent doing a LOT of tweaking.
The “breaking” it part.
It’s funny to play around with a program you’ve made, and be SO proud of your creation, to then have someone else run it and IMMEDIATELY find a flaw. For example, right at the beginning — my friend didn’t have a clue how to enter the ingredients. It hadn’t even occurred to me to provide explicit directions.
Although this issue had a simple solution, it demonstrated the value of collaboration and receiving an outside perspective.
A more complicated problem was how to avoid hitting my API key limit. I was only allowed 5 hits a minute. Well, when I initially wrote my code the valid_ingredients
method hit API three separate times, just to validate the user’s input. This meant my “Search again, with three more ingredients” function only worked if the user initiated a new search over a minute after the previous one. This one took hours to troubleshoot and it was right after I had declared myself “finished” — HA. Finally, thanks to some guidance from my cohort leader suggesting the use of an instance variable, I fixed it. (This is when @recipe_hits
was born.) And thus, I decided I had done enough refactoring and it was time to wrap it up.
Throughout this whole process my brain constantly flooded itself with an assortment of ideas on how to add more functionality. While exciting, I had to constantly remind myself this was my very first project to avoid feeling overwhelmed at all the possibilities. Especially as this particular project was supposed to be built in 3–7 days. What’s that expression? “Rome wasn’t built in a day.” Well, I can assure you, neither was my CLI application.
Hungry? Search for a recipe yourself here!