With Great CLI, Comes Greater Understanding: Phase 1 Project

Matthew Stahnke
9 min readDec 7, 2020

Until this point I have been far better at reading code than writing code. I have found, however, that reading code does not entail complete understanding. Enter this first unguided project. The ability to be completely in control of my program using Command Line Interface flipped the switch in my brain. I could now mold and direct the previously abstract classes and methods into a greater picture. Now, enough exposition. On to the reason we’re all here.

“…build a Ruby gem that provides a Command Line Interface (CLI) to an external data source. The CLI will be composed of an Object Oriented Ruby application.”

The Application I chose to build was simple: a My Hero Academia character viewer. Before we start, we need a few things…

An API (Application Programming Interface) to gather the information to create our Character objects: https://myheroacademiaapi.com/.

A Gem file Rest-Client to talk with our website: https://github.com/rest-client/rest-client.

A second Gem File JSON to parse through our gathered data and turn it from an unreadable block into comfortably listed marvel: https://github.com/flori/json.

Now we have all the external pieces to reach our goal. Time to get started with the basic build. Here is the file structure we are playing with:

All of our files live inside the my_hero_api folder which contains our Gemfile, Gemfile.lock README.md, two sub folders bin and lib. The bulk of our program lives inside the lib folder, which we will look closer at later. The bin folder only has our run file — what we will use to open our program with the terminal. Since run seems important to our program maybe we should start there.

These three important lines of code are what made me start to understand the flow of all of my programming.

On Line 1 we have our shebang. This is what tells the terminal that this is a ruby file.

Line 3 is the built in ruby method require_relative, which is telling our program to copy and paste all of the data inside the file path “../lib/environment” into this run file. We’ll see how powerful this is when we take a look at environment.rb.

Line 5 creates a new instance of the Cli class. It takes the information we got from the require_relative in line 3 and calls the class method start to begin interacting with the user. In this program it looks like this:

Three lines and our program is up and running. Where did it all come from? The aforementioned lib folder! Line 3 did the bulk of the work for us, so let’s look at the file located in the root of the lib folder, environment.rb.

Look familiar? It should. This is where we witness the power of require_relative.

Lines 5–7 are all following their file paths to copy and paste from their respective files: character, api, cli.

Lines 1–3 look similar to lines 5–7, but the difference is the built in ruby require method. This basically does the same thing as require_relative, but we use require when referring to files that are external, in this case, our gems. By putting require we are telling other developers who look at our code that this code isn’t contained inside our file tree.

Just like the run file, the environment has a small amount of code but a lot of power behind it. Why do we do it this way? The reason for this is the same reason we do a lot of things a certain way: to keep things simple, clean, and future proof.

Let’s talk about these gem files I keep bringing up:

Gem files are external files that we can install into our program so we don’t have to write that code ourselves. What we see here in the Gemfile is the code to tell our terminal where to find these gems so that we can bundle install these files into our terminal before running our program. We do this so when we call require on a gem its code will exist inside our terminal and we can use it.

Now that we know where the gems come from, let’s see what they do by learning how to grab information from our api.

This is the trickiest part of the project. Without the api, we have no data to fill our character viewer. Let’s break this down by method.

base_url is a method that we made so we don’t have to type out the url string every time we use it — simple, clean, and future proof.

load_data is a method that we will use in the cli to load in all of the data that is created inside of the next method: load_characters.

load_characters is the method that is pulling all of our information down from our api with the rest-client gem. From there it takes the response from rest-client and the json gem parses information into more readable data. We then iterate the result of that data into character_data, which finally creates all of our new Characters. A lot goes on in that chunk of code, and it’s further complicated due to the fact that we have to iterate through 17 pages of the website until we have pulled the response from each page. This is why on line 13 we start an until loop. Rather than typing out this block of code 17 times typing out the url for each page, we iterate through page_number. On line 18 all 318 instances of our character class are created. The until loop is fulfilled because our page_number count has now reached 17.

Time to look at the framework for the character class.

The Character class is our character object factory. When we create our characters the Character class is choosing what information from our api to store. Then the instance of each character stores itself into the “@@all = [] array so that each character can be called upon in our cli. It should be noted that when we call the all method in the cli we are limiting it to the first 100 entries as stated by our all method here in the character class.

What does this look like in our program? Well, I’m glad you asked.

After selecting the option to see all the characters, we get a long list of characters. We are then given the option to select a character by their number and are given their character details defined by the character class. It’s a lot of code for such a simple program, but it’s time to bring it home and see the code that creates the command line interface.

This is where that switch flipped. Because I can read what is happening inside each individual class, I can finally start putting it to work for a purpose. Looking back to line 5 of run we see “Cli.new.start” This line calls a new cli class and calls the start method. We now begin at the start method in our Cli class. It puts out a welcome and a loading message to give our user the idea that we are waiting. Next, we call “Api.load_data”. This is where we can see the program populate our data in real time — through waiting for the main_menu_options to print. From this point forward we can follow where the user is going right through our code via their inputs and by seeing the methods that are called from the result of their input. We now have a visual understanding of all the codes from point A to point B.

To note: there are a few methods in Cli that are not just directing the user through the program. The method that gives the user control — get_input (on line 31) puts out some space to make the print “Enter Choice: ” line have breathing room so the user can tell where they are typing more easily. Later, gets.chomp gets the input from the user typed next to “Enter Choice: ” because we called print on that line instead of puts. We write this method so we don’t have to type this code every time we want user input — unlike the puts good bye call that is on line 25, 66, 94.

Next, list_characters. Here, we are iterating through the @@all array in the Character class and offsetting the index number by 1. Now it will put out an index number next to the character id data we display to the user. Notice that we are taking in the character id rather than the name to be displayed to the user because this api is overly comprehensive with the characters it has information on. If we display the characters from the name value we end up with blank entries in our list, which makes it look like we don’t know what we’re doing. Because we display the character id instead, we need to gsub all of the underscores and percentage signs out of the ids to make it look more presentable.

Finally, let’s examine the if statement on line 56 of the character_menu method . This statement tells the program to read the users input as an integer that needs to be between 1 and the length of the @@all array in Character. If their input meets those conditions it offsets the input to be the proper index number, picks the character assigned to that index number, and puts out the character detail defined by our print_character_details method.

Understanding these methods, we can see the route the user would take through the if, elsif, and else statements from each menu. If you’d like a live explanation of the code *click here* for a video of me walking through the program. https://youtu.be/nsDsmHj2cdw

Now that I have control of the program I’m writing, I’m finally starting to have fun writing my code. I can see the whole landscape of what is happening in my program. I’m excited for what comes next in my journey at Flat Iron and beyond.

As my Uncle Ben would always say: “Peter, with poorly constructed puns, comes great judgement.”

To not leave you on a movie reference, here is a bonus tip for windows users that helped me when writing this blog — When you hit Windows key + S +shift you can create a snip of your screen that goes directly to your clipboard and can be pasted anywhere images can be posted.

Stay swinging!

--

--