My first CLI program written in Ruby

Yingqi Chen
6 min readApr 10, 2020

Feel free to click daycare-CLI-project to check out the full repo.

In the last article, I talk about how I integrate YELP API into my CLI project. And this article is about the whole CLI project: how do I plan it? how to avoid repeating yourself? how do I avoid making assumptions?

I have been working on my first CLI program these few days and to be honest, it is a big challenge to me. It is my first time building something totally from scratch–it intimidated me when I saw the task but turned out fun in the end, luckily. And it occurred to me that what I can do with programming is not only fancy grand things which are so far beyond my reach right now, but also some little and practical things like what I did this time.

My CLI program is called `top 10 local daycare centers`, what it tries to do is returning users with the top 10 local day care centers using the zip-code that the user provides. I believe it still needs some improvement like pretty interfaces but it is already helping me and I feel a sense of achievement from having implemented what I want to do.

How do I start?

For all these people who are as scared as me in the beginning, I recommend a building model that I learn from a video I have seen. That is called outside-in model which means, imagining a user sitting in front of the program, what kind of interface you want to present, like greeting the user first, and then what choices you will offer him and how the program is going to react to different user choices, etc. Here is an outline of the workflow I made:

  1. Welcome a user and explain what does this CLI do
  2. Ask for a user’s input for zip codes, decided if that is a valid input
  3. Search through yelp and get data
  4. Store data with Center object(Using data to make Center objects)
  5. Show the user a list of center names
  6. Ask the user which one they want to know more
  7. Quit or go back to step 2

You can always improve it later so to add a feature to the program or to just add more details. I add another function to it later, to ask users if they want to know more information about other daycare centers, if they say yes, then will be brought back to the list again instead of going back to the root menu and re-entering the zip code, saving one step, therefore, saving their time. That makes work much easier!

Despite my plan, I was lost in the middle when implementing step3. I tried to use yelp’s API instead of plainly scraping data from it. However, I am totally new to API, so I spent tons of time doing research and asking professionals, which showed me a lot of ways to do it, yet I was not smart enough to understand it. But luckily I was smart enough to listen to my tutor Beth and turned to work on the other part of the CLI project first.

That is really helpful because I can always build the main bone then adding some flesh to it slowly. That is more time-efficient to save the hard part in the last and try to build a minimum viable product (MVP).

By doing that, soon I finished my #call method, which is the main method in my main bone and then I started to fill up the flesh in it. And then, it will reveal to you how you will need other classes like `Center.class` and `Scraper.class` to implement what you want to achieve.

How do I perfect the project?

1. don’t make too many assumptions

It is very important to not have too much assumption in the program because it will break your program easily. Let’s say in step2, if a user gives an input of strings instead of numbers, or give out a random 4-digits or 3-digits number, it will break because of the way I take care of the numbers.

Also, very critically, when I try to make an object, attributes returned from yelp API can be quite inconsistent: some might have the attribute `address 1`, others not; some might have the attribute `phone_number`, others might not. Therefore, I made sure all attributes are existed, so the program will not break easily, hopefully.

For example, in the following codes, I have to make sure a center has an attribute of `phone_number` before I put any of these assumed attributes.

if center.phone_number   
puts "Phone number: #{center.phone_number}"
end
if center.url
puts "Yelp page: #{center.url}"
end

2. DRY (Don’t repeat yourself) and encapsulation

I tried really hard to avoid repeating myself and use encapsulation more. They are kind of the same thing–encapsulation helps with avoiding repetition.

If you encapsulate something you want to do in one method, next time you use it, instead of copying a whole bunch of codes, you can just call it–that is easier and codes look cleaner, in my opinion.

What’s more, when you have to change code, you don’t have to bother changing everywhere but only the original method, which will change the wherever reference to this method too.

Also, I love encapsulation when I need ending! Look at my codes and you will find two methods for ending here. Here is one example:

def ending
puts "Do you want to re-enter a zip code? [Y/N]"
input=gets.strip
if input.upcase=="Y"
call
elsif input.upcase=="N"
else puts "Wrong input!"
puts "Give me only 'Y' or 'N' please."
ending #if no qualified input, then go for another "ending" loop
end
end

You see, I need encapsulation because when I check the validation of user input, if users give me a bad input, I should be able to restart the whole process. How can I do that? I just call that method within that method! That is much simpler than `#while` or other roundabouts.

3. Saving work by using instance variable

The last but not least thing I learn from the project is to make a variable instance variable if you have to keep using it.

I do that when I have to deal with the method `#list_centers`. That method is made for showing a list according to the zip code that user inputs. In this step, a zip code(the input) is passed to `#list_centers`.

Later on, after users choose a center from the list and know more about it, they can go back to the list and pick another center from it. That means, `#list_centers` is called again. But how does it know about the zip code as its argument? Should I ask the user again?

No. I don’t have to, and I don’t want to. The user will hate it too! All I need is storing the input right after the user gives me as an instance variable called `@zip`. Anytime I need it, I just call it, and don’t have to worry about asking for zip codes again and again!

Extra notes

Last but not least, I want to list some of my notes here. It might look very small, but that does cause my headache, and I hope that I won’t make those mistakes again!

When using `require_relative`, you have to use a relative path instead of an absolute one. It is absolute to the current file you are writing codes on, not to which directory you are in right now. To require an document in another directory, if I want to go up one directory, I need to use `../`.

Let’s say I have a root directory that includes two sub-directory: `bin` and `lib`, if I am writing in a `bin/exec.rb` and want to require a `lib/hello.rb`, I should put it like this : `require_relative '../lib/`. To require documents in the same directory, using `./` is fine.

The end

Thanks for reading!

--

--

Yingqi Chen

Software engineer and a blockchain noob. Excited about the new world!!LinkedIn:https://www.linkedin.com/in/yingqi-chen/