Ruby CLI app project
Tags: ruby, flatiron, beginners, cli
Published: 2021-08-26 04:29:57

This project was my first experience coding something from scratch without having a guiding hand help me figure everything out (aka rspec test suites), and I feel like I definitely leveled up my game. 

I decided to make a CLI app that scrapes for data on a zip-code's 5 day weather forecast. I got this idea from my dad, who is always asking me what the weather currently is or is going to be throughout the week. I originally tried to scrape from a Google search, but had no luck getting the data for some reason, regardless of what css selectors I used, so switched to Weather.com instead and it was much more straightforward.

I had a general idea of what I wanted the app to do and how many classes there would be interacting with each other, so translating that to a flow diagram was very simple and made the overall functionality even clearer. 

I originally had created an app that just scrapes for a location's rain chances, and then realized that it did not go "one level deep" as required by the project guidelines, so my flow diagram is not entirely accurate. The overall interaction between the 3 classes is still the same, however.

I ended up creating a Search class, a Scraper class, and a Weatherclass. The Search class had the responsibility of taking a zip code that the user inputs and finding the Weather.com url for that zip code. The Scraper class took that url in the form of a Search class instance and scraped the page for info on the 5 day forecast and pushed the High temp, low temp, and rain chances into an array in the form of a hash. The Weather class then took an instance of the Scraper class and assigned each hash from the array to a different Day variable. The bin file would take a user's input of a zip code and create an instance of the Weather class with it and ask the user which day's weather data they wanted to see via CLI. After outputting the forecast, the CLI then asks if the user wants to see the forecast for another day. If the user says no, the CLI asks if the user wants to look up the 5 day forecast for a different zip code. If they user says yes, the program runs again; if the user says no, the program exits. 

```
class Search

    attr_accessor :location, :zip_url

    def initialize(location)
        if location < 501 || location > 99950
            raise ArgumentError 
        else
            searcher(location)
        end
    end

    def searcher(zip)
        fixed_zip = zip_convert(zip)
        uri = Addressable::URI.parse('https://weather.com/weather/today/l/' + fixed_zip)        
        @zip_url = uri.to_s
    end

    def zip_convert(code)
        zcode = code.to_s
        while zcode.length < 5 do
            zcode = "0" + zcode
        end
        return zcode
    end
end
```

```
class Scraper

    attr_accessor :location, :outlook

    def initialize(location)
        scrape_rain(location)
    end

    def scrape_rain(zip)
        @outlook = []
        link = Search.new(zip).zip_url
        html = URI.open(link)
        weather = Nokogiri::HTML(html)
        weather.css('.DailyWeatherCard--TableWrapper--3mjsg').css('.WeatherTable--columns--OWgEl li.Column--column--1p659').each do |w|
            i = 0
            if w.css('.Column--temp--5hqI_')[i].text == "--"
                @outlook << {
                    :high => weather.css('.CurrentConditions--primary--2SVPh .CurrentConditions--tempValue--3a50n').text,
                    :low => w.css('.Column--tempLo--1GNnT')[i].text,
                    :rain => w.css('.Column--precip--2ck8J .Accessibility--visuallyHidden--2uGW3')[i].text
                    }
            else
                @outlook << {
                    :high => w.css('.Column--temp--5hqI_')[i].text,
                    :low => w.css('.Column--tempLo--1GNnT')[i].text,
                    :rain => w.css('.Column--precip--2ck8J .Accessibility--visuallyHidden--2uGW3')[i].text
                    }
            end
            i += 1
        end       
    end 
end
```

```
class Weather

    attr_accessor :location, :day1, :day2, :day3, :day4, :day5

    def initialize(location)
        chances = Scraper.new(location).outlook
        @day1 = chances[0]
        @day2 = chances[1]
        @day3 = chances[2]
        @day4 = chances[3]
        @day5 = chances[4]
    end
end
```

Overall, the basic structure and relationship of these three classes came easily, but the real issues arose when I tried to put them all together in a bin executable file. After testing the app in irb, I kept getting errors in the output data, and I eventually realized that the user input was being converted from a string to an integer, so any zip code with leading zeros was being altered. After hours of googling, I found out the solution to this was to convert the integer back to a string using `integer.to_s(8)` which would use a radix base of 8 to convert the integer to its' original value, just without the leading zeros. Converting this string back into an integer did not alter the value, so I added leading zeros as necessary while it was still in string form. This roadblock in the project took the longest to figure out compared to every other issue (most of which were just typos), and I was incredibly relieved to see my code working at the end of the struggle.


This project taught me a lot about the process of coding from scratch and what it takes to build something that works exactly how you want it to. Although this CLI app is not much compared to other projects I will complete in the future, I am very proud of what I have done. Before starting the Flatiron Bootcamp, I never would have believed I could actually make something using code.