Taco Town

Garrett Bodley
5 min readNov 14, 2020
hurray tacos!

My first project with the Flatiron school was to design a CLI app (Command Line Interface) that pulled data from any API (Application Programming Interface) available online. I decided my program would be about tacos

The API

I chose an API called Taco Fancy which hosts a collection of various taco ingredients, sorted by type, each with a recipe and a name. In order to access that data, I first needed to write a program that sends a GET request to the hosting website.

api.rb

I wrote a class Api that used the httparty gem to send a get request to the data endpoint. The method get_request utilizes string interpolation to dynamically generate a URL depending on an input argument, with the default arg set to "random" .

  ENDPOINT = "http://taco-randomizer.herokuapp.com/"  def get_request(query="random")
url = "#{ENDPOINT}" + "#{query}/"
uri = URI.parse(url)
response = Net::HTTP.get(uri)
end

This method, however, only returns a literal string from the database. Oh no!

Api.new.get_request
=> "{\"base_layer\": {\"url\": \"https://raw.github.com/sinker/tacofancy/master/base_layers/moroccan_lamb.md\", \"slug\": \"moroccan_lamb\", \"name\": \"Moroccan Lamb\", \"recipe\": \"Moroccan Lamb\\n=============\\n\\nA Differently Spiced Meat Than Your Usual ...(etc)
tacos can be difficult sometimes

In order to manipulate this data, I need to utilize a different gem, json . This allows the computer to parse the JavaScript Object Notation and return a usable data structure. If I call my previous get_request method inside of the JSON object and allow it to accept that method's input as an argument I could both get and parse the requisite data all in one line. Pretty handy!

I also defined a get_random method that is functionally identical but doesn't accept an argument, causing get_request to use the default query of "random". I felt this was more descriptive than calling search_by_type() with no argument whenever I wanted a random taco.

def search_by_type(slug=nil)
JSON.parse(get_request(slug + "s"))
end
def get_random
JSON.parse(get_request)
end

And our new return:

=> {"base_layer"=>         
{"url"=> "https://raw.github.com/sinker/tacofancy/master/base_layers/rajas_poblanas.md",
"slug"=>"taco_de_rajas_poblanas",
"name"=>"Taco de rajas poblanas",
"recipe"=>
"Taco de rajas poblanas\n======================\n\n* Bunch of poblano peppers\n* Onion\n* Tad of oil\n* Mex...(etc)

A hash! Finally, a data structure we can manipulate!

Ingredients and Tacos

I wanted my app to let you browse the various ingredients and select the ones you liked to save as a taco. This was accomplished by creating an Ingredient class where each instance stored the info of each ingredient, and a Taco class that had a recipe stored as an array of many ingredients.

taco.rb

The Taco class was relatively straight forward, with a few instance methods such as .recipe (returns a Hash with keys that correspond to the taco “layer” and values that were Ingredient instances), .save (saved the current Taco by shoving it into an array saved as a class method), .full? (checks if the every layer has an assigned ingredient), .add_ingredient() (shoves a new ingredient to the @ingredients array), etc

def add_ingredient(ingredient)
@ingredients << ingredient unless @ingredients.include(ingredient)
end
def recipe Hash.new.tap do |recipe|
@ingredients.each {|ingredient| recipe[ingredient.type] = ingredient.name}
end
end

ingredient.rb

The Ingredient class was fun as I got to use some metaprogramming to dynamically create attributes and assign values to them using the Hash returned by .search_by_type() .

def self.load
Api.new.tap do |api|
api.get_random.each_key do |type|
api.search_by_type(type).each do |ingredient|
Ingredient.new(type: type, ingredient: ingredient)
end
end
end
end
def initialize(type:, ingredient:)
@type = type
ingredient.each do |key, value|
self.class.attr_accessor(key)
self.send("#{key}=", value)
end
@@all << self
end

Now I can call the class method Ingredient.load to query the api and instantiate a new Ingredient instance for each “type” of ingredient (which layer it belongs to). Using metaprogramming, that instance will also assign itself all the corresponding values and labels it needs to function. Hurray!

we’re getting closer to our digital tacos

The CLI

The CLI was a bit challenging, as there are a lot of conditions and inputs required for the user to navigate between different types of ingredients, view those ingredients’ recipes, add an ingredient to a taco, browse tacos, etc.

I decided to write helper methods for printing the prompt and getting the user input, using an until to loop until the input was valid. For example:

def create_taco
input = create_taco_input

if input == Ingredient.types.count + 1
welcome
else
choose_ingredient(Ingredient.types[input-1])
end
end
def create_taco_prompt
clear
puts "Please choose an ingredient type:".bold
Ingredient.types.each_with_index {|type, index| puts "#{index + 1}. #{type.split("_").map{|word| word.capitalize}.join(" ")}"}
puts "#{Ingredient.types.count + 1}. Main Menu\n\n"
end
def create_taco_input_loop
input = get_int
until input && input > 0 && input <= Ingredient.types.count + 1
invalid_input
create_taco_prompt
input = get_int
end
input
end

From there on out, I just needed to fill out the possible pathways a user could follow and make sure I didn’t leave any hanging parenthesis.

Welcome to Taco Town

look ma! a CLI program!

It was a lot of fun writing this code and I definitely learned a lot in the process. I made many mistakes along the way and with each one came an opportunity to deepen my understanding of object oriented programming. I’m definitely looking forward to seeing what future programming challenges are on the horizon.

If you’re feeling hungry after all this taco talk, please feel free to swing by Taco Town and see what recipes lie in wait. Thanks!

--

--