I Built an OO CLI App that utilizes the Rest Countries API to provide basic information on any Country in the World. Here's How.
For my first project at FlatIron, I was given the task to build a Ruby CLI application which is meant to be my first portfolio project. The app is supposed to utilize basic programming concepts, object-oriented programming, and work with third-party data by scraping a web page or accessing an API. My application uses the REST COUNTRIES to fetch a list of all the countries and individual country data for the user to interact with. The user can enter a country to get basic information such as capital, population, language, currency, region, and sub-region. The user also has an option to look at a list of all the countries in the world, and from that list, they can select a country that they want to get more information on. To close the program, the user can simply enter 'exit'.
Pre-requisites
My application utilizes these basic concepts:
Basic Ruby concepts(blocks, loops, variable, etc_
Object-oriented programming in Ruby(classes, attributes, object-relationships, etc)
Working with external data(APIs)
Using the REST COUNTRIES API
I choose to work with the REST COUNTRIES API because it seemed like the most interesting to me. Actually, I take that back, the REST COUNTRIES API was actually my second choice. My first choice was the Marvel API. I wanted to use that API to get access to Marvels glorifying library of characters, but I failed at getting access. I got to the point where I was given an API key but there came many restrictions after that. Maybe I'll try it again on another project.
The REST COUNTRIES API was my second because I thought It would be cool to provide users with some information on any country in the world. Unlike the Marvel API, the REST COUNTRIES API requires no need for an API KEY.
Planning
My first initial step to planning out this project was creating a kanban board on my Notion app.
The board was because I could visually see tasks that had were not started, In progress, and completed. Having that visual representation helped me stay organized, but what I later realized was that I also should have started by building a wireframe or a flowchart detailing the users' experience and how each class would come into play. By not starting with a wireframe or flowchart I came ran into some problems when building my objects because I did not have a visual representation of the program would work. I eventually had to ask for some assistance to help me straighten out my processes. Towards the end of the project, I decided to make a flowchart displaying the processes is a user was to enter a country:
Allocating Responsibilities
In object-oriented programming, it is important to be aware of what tasks need to be done and then giving those tasks to different classes to execute. I decided to make the following classes based on my observation:
A Country class
Responsible for initializing instances(objects) of a country with variables and storing that object in a class variable called all.
Hold a Class constant variable that stores the names of every country inside an array.
An API class
- Fetch all the information from the API, and only format the names of every country.
- Fetch individual country data and format it.
A CLI class
- Access the data from the other classes
- Interact with the user
Build, Test, Learn, Repeat
My project did not require any gems, or I should say that I choose not to use gems for this project. The only files that had to be set before starting my project were the Executable and environment files:
#!/usr/bin/env ruby
require_relative '../lib/environment'
Cli.new.run
require 'pry'
require 'HTTParty' #allows me to request data from API
require 'json'
require_relative './mycountries/cli'
require_relative './mycountries/api'
require_relative './mycountries/country'
Building the API class was fairly simple since I didn't require an API key. Inside the method class, I built two class methods, API.get_country_list and API.get_country_info. The get country list method calls the COUNTRIES API, sets a variable and gives it a value of the data that was called. It then takes that variable and parses it turning it into JSON syntax. Lastly, the method invokes the collect method on that variable to return an array with only the names of every country in the work. The get country list method takes in an argument, which should be the name of a country. It then calls the API with the country name at the end using string interpolation. It then sets variables and gives it a value of the parsed data. Lastly, it instantiates a Country instance and matches its values from the parsed data.
class Api
def self.get_country_list
url = "https://restcountries.eu/rest/v2/all"
response = Net::HTTP.get_response(URI(url)).body
countries_list = JSON.parse(response)
clean_list = countries_list.collect do |country|
country["name"]
end
end
def self.get_country_info(country)
x = country.gsub(" ", "%20")
url = "https://restcountries.eu/rest/v2/name/#{x}?fullText=true"
response = Net::HTTP.get(URI(url))
parsed_response = JSON.parse(response)[0]
currency_symbol = parsed_response ["currencies"].map {|s| s["symbol"]}.join
currencies = parsed_response["currencies"].map {|c | c["name"]}.join
languages = parsed_response["languages"].map {|l| l["name"]}.join(", ")
Country.new(name: parsed_response["name"], capital: parsed_response["capital"], region: parsed_response["region"], sub_region: parsed_response["subregion"], population: parsed_response["population"], language: languages, currency: currencies, symbol: currency_symbol)
end
end
The main function of the Country class is to initialize all necessary variables for a country instance and to store every instance in a class variable.
class Country
attr_accessor :name, :capital, :region, :sub_region, :population, :language, :currency, :symbol
@@all = []
COUNTRIES_LIST = []
def initialize(name:, capital:, region:, sub_region:, population:, language:, currency:, symbol:)
@name = name
@capital = capital
@region = region
@sub_region = sub_region
@population = population
@language = language
@currency = currency
@symbol = symbol
save
end
def save
@@all << self
end
def self.all
@@all
end
def self.list
COUNTRIES_LIST
end
def self.find_by_name(name)
@@all.select {|c| c.name == name.capitalize}
end
def self.print_by_name(name)
if @@all.select {|c| c.name == name.capitalize}
end
end
end
The CLI class is built to interact with the user. Even though this is a back-end application, a design mindset is necessary to provide users with a good experience.
class Cli
def run
puts " "
puts "Hello and welcome to my COUNTRIES APP!" if Country::COUNTRIES_LIST.length == 0
Country::COUNTRIES_LIST << Api.get_country_list if Country::COUNTRIES_LIST.length == 0
puts " "
puts "Enter a name of a country to get more information about it."
puts "..Enter 'list' to SELECT from a list of all the countries IN THE WORLD."
puts "...Or Enter 'exit' to exit! "
puts " "
@country = gets.strip.downcase
if @country == "list"
print_list
elsif Country.list[0].include?(@country.capitalize)
print_country_info
elsif @country == 'exit'
puts "---------------------------------------"
puts " "
puts "Thank you for using my APP:)"
puts " "
puts "Bye!"
puts " "
puts "---------------------------------------"
exit
else
error
end
end
def print_list
puts " "
puts "Here is a list of all 250 countries. Type in a number to get more info on that country."
puts "---------------------------------------"
Api.get_country_list.each.with_index(1) do |name, index|
puts "#{index}. #{name}"
puts "---------------------------------------"
end
puts " "
puts "Enter a number from the list ^ to get more info on the country:"
input = gets.strip.downcase.to_i
@country = Country.list[0][input - 1]
print_country_info
end
def print_country_info
if Country.all.any? {|c| c.name.downcase == @country.downcase}
@country_object = Country.all.detect {|c| c.name.downcase == @country.downcase}
else
@country_object = Api.get_country_info(@country)
end
puts " "
puts "Here is more information on #{@country_object.name.upcase}:"
puts " "
puts "---------------------------------------"
puts " "
puts "Capital: #{@country_object.capital}"
puts " "
puts "Population: #{add_commas(@country_object.population.to_s)}"
puts " "
puts "Language(s): #{@country_object.language}"
puts " "
puts "Currency: #{@country_object.currency} (#{@country_object.symbol})"
puts " "
puts "Region: #{@country_object.region}"
puts " "
puts "Sub-region: #{@country_object.sub_region}"
puts " "
puts "---------------------------------------"
run
end
def error
puts "---------------------------------------"
puts "I do not understand- please try again:"
puts " "
run
end
def add_commas(num_string)
#Gives commas to a string of intgers, then converts that string into an integer.
#So if you want to pass in a number you first have to convert it into a string using to_s.
num_string.reverse.scan(/\d{3}|.+/).join(",").reverse
end
end
Conclusion
I've built static web pages with HTML and CSS in the past and also played around with Javascript but this is the first project that I have started and finished with an MVP. My biggest take away from this project is to always start with a wireframe or some kind of visual representation of what the application should do.
While building the application I also learned a lot about different countries around the world. I hope any be who uses it will get the same learning experience.
Fork/clone the project from my repo on Github and give it a go!
Happy coding!