For the final project in the Ruby object-oriented programming unit in Flatiron School’s Online Web Developer program, I am building command-line interface data gem. I’m incredibly excited to have a personalized project. Where else can you visualize something you wished existed and then bring it into existence? Bringing an app to life from a blank screen in a text editor feels almost like a violation of the first law of thermodynamics. Somethingness emerging from nothingness!
After an excessive amount of brainstorming with a friend, I settle on building a gem that spits out a random book excerpt and gives the user the option to get the title and author info if they are intrigued enough or to get another excerpt. Think Tinder for reading.
Stoked on my idea, I set forth to create a command-line work of art. An excellent intro video by Avi Flombaum at Flatiron guides me through the first stages of birthing a gem— the seemingly magic ‘bundle gem’ generates a Ruby gem with a premade file structure and the base files I need to start a project. Charge!
To start, I think about the user’s journey through the app. I want the interface to be friendly, easy to read, and easy to interact with. I add some methods that welcome the user and hardcoded a genre list to display. I start by following along with the video, pausing frequently to do as Avi does. A blank screen can be an intimidating thing, and a little scaffolding goes a long way.
I begin to realize I can make the code more modular and more pretty using iteration. I feel the training wheels start to come off. Drawing on patterns learned from lessons and labs, I create a Book class and a Genre class. I envision an initial scrape that grabs and stores all the info about all the books. I’m thinking it’s all downhill once the info is ready for easy retrieval in an array of hashes. Spoiler alert: this is not the best approach to take. In my naive optimism I forge ahead.
Before getting real data from a real book website, I want to get the app functionality down and ready to receive scraped data. To do this, I:
- Add a method to create a new Book instance, so that the book will have the correct properties and be added to the appropriate collections. Also one to find a Genre instance by name.
- Link the two classes. Genre gets a @books instance variable to hold related Book objects, and Book gets a @genre instance variable that links to a Genre object. New relationships are being forged.
- Add a method that displays the @text value of a random book hash from a Genre’s @books variable followed by one that auto-generates Genre instances based on my selections.
As I work through these steps, I start to see the data in a different way. I find myself visualizing the return value as a solid object that can be passed around. Data from one place can effectively be wrapped up in a method and sent off as a package to a different part of the app. When the method is called in another place, the package gets unwrapped and the return value is there ready to go.
Say my hungry friend in Colorado wants a bowl of Momofuku ramen from New York.
The data (ramen) is born in MeInNY ’s get_dinner method, which sets the @dinner variable to the string “Spicy Hozon Ramen” and also returns it. The return value is key. That data can now be accessed somewhere else by calling the method. My FriendInCO calls my get_dinner method and whatever that method returns, in this case Spicy Hozon Ramen, emerges in CO, still steaming. In fact, I could just as easily send that tasty, tasty payload to a FriendInNorthKorea as long as they have access to my method. If only delivering food were as easy as delivering data!
Next I work on the scraping functionality. It feels very uncharted and exciting — there’s no solution here and the problem is as individual as my chosen website, Book Daily. Armed with the homepage’s HTML and an idea of where I want to end up, it’s one step at a time. I manage to scrape the url for a genre page, which becomes the input for a method that scrapes that genre page for individual book urls. That output in turn is used to drill down to individual book info. Real data!
Now that the individual pieces function as expected, it’s time to merge them into one beautiful machine. Now I can get all the info about each book stored in a hash, with an array for each genre composed of all the relevant book hashes. However, now that I’m actually doing the scraping, it’s taking a really long time. I ask myself, “Is this what I really want?” It’s an apt question to ask oneself periodically.
I realize that getting the details for just one random book at a time as the user requests it is a much more efficient approach, as long as duplicate excerpts are not shown. So I’ll also need a way keep track of the books that have already been shown. This is going to require reconfiguration of my whole scraping functionality as well as all the methods that interact with the data.
For a brief moment I hesitate. At first it seems as though all that I’ve poured into this one avenue will have been in vain. But I know I can do better and now that I have a clearer idea of where I’m going I know that I must do better. Besides, I can still use the general scraping approach, just on demand rather than all at once. I can also keep some of the code for methods that handle the data, just need to change some things around. As in life, I’m a proponent of assessing the situation periodically and being free to change direction if needed.
It’s not the smoothest process but I manage to integrate my new idea and voila! The app runs much faster. With all the testing I’m doing, I find myself wishing for access to a list of all liked books. I add an instance variable for each book that indicates whether the user has liked it and am able to show a list to the user upon request. I’m really into this new feature.
Throughout the whole building of the app, I tweak the interface constantly. How exactly do I want to interact with the user? How can I personalize the experience? When is exclamation point overload? How to visually separate prompts and returned information? How much text is enough but not too much? How many books to choose from randomly? Should I add delays to give the user time to process?
Since there are certainly no right answers, it’s all trial and error based on what I, as the user, prefer. I keep noticing things that don’t quite look or feel right. I change the interface in nearly every commit, sometimes changing it back in the next. It’s fun thinking ahead to the user experience.
I also find myself getting distracted by the books themselves. Like if I’m testing what happens when a user chooses not to get the book information but I actually like the excerpt, request the book info in spite of myself, and have to restart the testing. Because I’m enjoying being the user, the testing part is prolonged and frankly pretty interesting. This bodes well for the app I reckon.
After I work out a few miscellaneous bugs and make a few last interface changes, I’m happy with how it runs. I refactor to make method and variable names more descriptive, modularize methods, and reduce repetition. I am a proud new parent of a cool little gem!
Here are the key takeaways and lessons I learned along the way:
- Think about how information gets passed around.
- Challenge assumptions. Each error message is an opportunity to review assumptions and maybe learn something new.
- Put a little extra effort in planning. It could save unnecessary work down the line.
- Embrace awkwardness, discomfort, and ambiguity. Progress, sometimes the best progress, is made even when things look a little hazy. There’s lot of awesomeness in the unknown.
- Think about what I really want. Sometimes I can see the situation more clearly once I’m inside it. Sometimes it’s okay to change direction or start over.
- Draft the blog post as I go along. Writing about the process is hard, and even harder after the fact. The more time I have to work on it, the merrier.
Though I’m at the beginning of my coding journey, I’m incredibly excited for the road ahead. The ever expanding realm of technology guarantees that I’ll never run out of lessons and learning, and I can’t wait to see what comes next. Forward ho!