'Trail Finder - CLI Data Gem Project'

Posted by Mark Kanko on May 13, 2019

My first portfolio project is complete.

I have written my first program, it works and mission accomplished. There is a sense of accomplishment in this moment that is multifaceted and substantial. It’s more than just being done and able to say that somehow I pulled it off, and hope I don’t have to do that again. That would be a sad and diminished take away from all that this experience had to offer. No, this sense of accomplishment is of a more substantial quality, because more happened that just a few lines of code that operate to make a basic little cli program work, and I didn’t just barely pull it off, I worked the problems, struggled, learned and actually in the end achieved. This was such a valuable growth experience, as so many of the concepts we’ve been working to understand were required and implemented. Like so many pieces of a puzzle, these pieces have come together to produce, not just a working program, but a more robust understanding of the how and why, at a level of comprehension facilitating an appreciation of how cool and trick all the moving parts are.

I spent the weeks leading up to this project intimidated and fearful. I can’t say I dreaded it, but I wasn’t really looking forward to it either. Now that it is completed, as challenging as it was, I can’t wait to do it again, I can’t wait to take on another project and work through the build process again. That “Can’t wait for the next one!” feeling is just one facet of an accomplishment, which in and of itself, is rewarding, exciting and worth every bit of the rigors involved to get here.

After the last few labs of the Object Orientation Section of Ruby, I was mentally beat up and I had to really pick myself up to approach this project. I think that the collaborating objects lab really took its toll. Unseen at the time was the payoff that lab returned. But I was kind of a basket case going in. As I read the project requirements, it felt like I was starring up at Everest. Sounds kind of ridiculous now, because looking back, this project was just not that bad. Challenging? Yes, but not bad. That is the first big take away and lesson learned. It is a mistake to feel daunted by a project, a big lab, or a big code problem. Giving right of passage to that feeling just gets in the way of clear thinking and a clean approach. It gets in the way of what we are training to do, which is solving problems, every line of code is about solving a problem. It’s not a fire breathing dragon; it’s just a code problem to be solved. That’s what we do; we fix code, because if it’s not broken, it will be when we re-factor. Red – Green – Re-factor

Break it down and take it apart piece by piece.

For me, that started with my Project Build Notes. That is what I called a planning document I wrote and included in my project file directory. This was the first thing I did, because I was kind of frozen like a deer in the headlights at that point. It was the first thing and the smartest thing I did. In this document I outlined my program. I included notes from supplemental study groups I attended on the project, app description, purpose, build strategy, project requirements and a step by step sequence of what my program should do. This was invaluable. By the time it came to building out my cli-controller the flow control and some of the logic was easy because I had already pseudo coded a lot of it. Included in the build notes was repeated reference to the project core requirement/goal in my own words as I understood it.

“Build an app that uses collaborating object classes to interact with a user, acquire external data on request, use that data to create instances of objects with attributes and store those “smart” objects for use, and then return those objects, which know their attributes, to provide information, on demand, in a practical way to the user.

My app is called Trail Finder. It’s a program that vacationing Mountain Bikers can use to find Mountain Biking Trails in the Bend Oregon area. The user can look for trails based on how far they would like to ride (short ride, medium ride, and long ride) or just select trails from a list of all available trails. Once a trail is selected, information about that trail (condition, length, elevation) is returned to the user. The user can then select a detailed description of the trail and more information is returned to the user. The user can continue this process to view multiple trail options before exiting the program and head out for a sweet bike ride.

In my build notes I outlined a Domain that included a CLI Class, Scraper Class, and Trail Class. Each one of these object classes have a singular responsibility and then collaborate with the others to achieve the over all mission objective of returning usable information from an external source, on demand, to a user.

This is how it works.

From the bin folder, the executable file ./bin/trail-finder runs the program by calling:

TrailFinder::CliController.new.call

This line creates a new instance of cli-controller and calls the #call method on that new object of cli-controller. At the same time ./bin/trail-finder requires ./config/environment in order that anything needing the environment file, where all necessary files are required, can load it with the one line call:

require_relative '../config/environment.rb'

Pretty Cool!

The cli-controller has the singular responsibility of interacting with the user, through messages and prompts puts to terminal, and then receiving and using input from the user. Once the #call method is called in the cli-controller the user is greeted with a message and initial instruction for use. Then the cli-controller calls to the Scraper class, whose singular responsibility is to acquire data from an external source:

TrailFinder::Scraper.scrape_trail_list

The #scrape_trail_list method uses Nokogiri and Uri to open the webpage and parse the HTML to access the data with CSS Selectors. The data is set equal to variables:

trail_name = awesome.css("div.trail_name").text 
condition = awesome.css("div.trail_status").text 
length = awesome.css("div.trail_length").text.gsub(/[~ miles]/, "") 
elevation = awesome.css("div.trail_elevation").text.gsub(/elevation/, "") 

Here comes the cool part!

At the end of the #scrape_trail_list method, the last line of code calls .new on the Trail class:

TrailFinder::Trail.new(trail_name, condition, length, elevation, trail_url) if trail_name != "Trail Name"

This line of code calls to the trail class who’s singular responsibility is to create new instances of trail objects and store them for use. But those trail objects have attributes and those attributes are the data set equal to variables and those variables are passed in as an argument with .new so that when a new instance of each trail object is created, it is initialized with its attributes stored in the variables. These new trail objects are then stored in an array set to the class variable @@all for use.

So these trail objects are chilling in this array and each one knows what it is, knows its name, and knows its other attributes.

That’s Rad!

That Just Happened!

All that just happened with the second line of code in the #call method of the cli-controller, which collaborated with the scraper class, which collaborated with the trail class. The next line of the #call method calls the #list_trails method which puts a list of trails along with a prompt for the user. Now the user has the option of selecting trail objects that know their data attributes, and with a bit of conditional logic returns that information to the user.

The Coolest Part.

In order to acquire the trail description data and return that information to the user, the cli-controller gets the input from the user, which is a trail selection, which is a trail object and sets it equal to a variable:

trail = @trails[input.to_i-1]

Now the cli-controller reaches out to the scraper class again and calls a second scraping method because the data is found on a separate page of the source website:

self.scrape_trail_description(trail)

What is really trick is the #scrape_trail_description is called with an argument of a trail object set to the variable “trail”. The trail object knows its attributes and provides its attribute of “trail_url” to Uri to open that web page. The page is parsed with Nokogiri and the data is acquired with CSS Selectors and set equal to the “:description” attribute of the trail object that already exists in the trail class and available through an attr_accessor, adds that attribute to the trail object, and the cli-controller then interpolates the value of the trail objects attribute “#{trail.description}” and returns that information to the user.

Sweet Trick!

All said and done, this project required the implementation of everything that we have learned to date. The ability to reach back, access and use those tools, no matter how arduously, is another rewarding return for the work and a facet of what has been accomplished. When I first looked at the project requirements, I didn’t feel ready to build a working program that would meet the requirements. The truth is I wasn’t. I had to build the project in order to be ready to build the project.

I think another facet of the pay off is that working this project through to completion has taught me to be a more effective Software Engineering Student. Maybe easy to claim the status of student, but credible, legitimacy and truly effective learning, that acquires skills and knowledge, maybe this is really earned through trial by fire.

No More Dragons!