Wednesday, January 29, 2020

Back in my Students' Shoes

Now that I am actually a "student" again, I could probably write dozens of posts with this title ... but this is the first time I've felt that head-spinning brain explosion that I suspect many of my math or CS students may have felt upon being introduced to a new topic that was heavy with both concepts and terminology.

Flashback to a typical AP Computer Science class for my students: I introduce some pretty rich content, demo some code, and have them write some code together. They do some legit troubleshooting, often using what they just learned, and then - Eureka! - by the end of the class period, they have something working and they feel good about it. The next day, they walk into class and I ask them to write an explanation of what they did yesterday, or connect the terminology to the content, and I get ... *blank stares*. Which always made me wonder, "Did they even learn anything at all the previous day???"

Of course, it was easy for me to stand on my metaphorical pedestal, having taught the content multiple times, and claim to students that they didn't truly understand something until they could explain it. I still think that is technically true ... but I also now see that there's a bit of a gray area where your brain may have started to make some significant connections, and is primed for deep learning, but is just not quite there yet. Which is all a long-winded way of saying that after a mind-blowing week of jumping into SQL, ORM, and ActiveRecord labs, I felt AMAZING about how MUCH I was learning ... and then I stopped to think about what these technologies actually are, and how they relate to each other, and I (like my students) had nothing.

So, here's my attempt to take a step back and articulate my understanding of these tools and their relationships to each other. Think of it as a little check for understanding that "teacher-me" assigned to "coder-me" before moving on.

SQL (Structured Query Language): The language of managing databases. It was fun spending a couple of days training my brain to thing along the lines of queries and "how do I access that piece of information I'm trying to find, that doesn't seem like it should be so complicated???" When we started learning about relational databases (i.e., tables with foreign key columns that relate them to other tables), the connection to object-oriented programming became clear: tables have relationships just like classes have relationships! Which brings us to...

Objected Relational Mapping (ORM): My understanding of ORM is that it allows you to access and modify a relational database using an object-oriented programming language (like Ruby). The big idea is that classes are mapped to tables, while individual rows in those tables are instances of the class they represent. This was a big "a-ha moment" for me: so far, everything we had been learning seemed to use toy examples and I still felt like something big was missing. For example, when instances of a class (like a Facebook user object) are created, are they really stored in an array called @@all??? Does an insurance company's software store its thousands of policies in a list??? I'm guessing not, and now I'm thinking these objects actually get stored in databases somewhere.

We were writing ORM's in Ruby, and the cool thing about these was that they used both Ruby and SQL so the connection between the object-side of things and the table-side of things was really transparent. To set this all up, we created a database in our environment file (that responsibility shouldn't fall to a specific class). We also created this special Ruby object that represented the connection between our Ruby program and the database we had just created.

Then, we were able to call the execute method on this special Ruby object and pass in raw SQL! This allowed us to, say, define a class method for SomeClass where we queried the table representing SomeClass to find a class instance with a specific attribute.

Just like with anything, after awhile, you get the feeling that you are doing a lot of work by hand that would be duplicated if you were creating an ORM for a different set of classes and a different database. Creating a table, saving instances to that table, and then finding an instance by an attribute all seem pretty standard. Which brings us to...

Active Record: Active Record is "just a gem" (this was emphasized multiple times) that gives you access to a bunch of standard ORM methods (create, save, find_by) you'd otherwise have to build from scratch. One cool thing about Active Record is that once you create a table called, say, users, that has columns for user_name and user_location, then your User class will automatically get instance variables corresponding to those columns! Pretty nifty, huh? (Although, I am grateful to have built some ORM's by hand first so that I could really see this relationship in a concrete way.)

Another feature of Active Record that blew my mind was the idea of creating class associations. Class associations are a concept I'm familiar with. When I taught AP Computer Science, my students did a bank account project and understood that "The Bank 'has many' BankAccounts" and that "a CheckingAccount 'is a' BankAccount", etc. Active Record lets you abstract these relationships using keywords like belongs_to and has_many that create those associations for you. Despite this being really cool, it's also not magical: what I've called "keywords" are really just "macros" that build methods for us - the same methods we'd be building if we were creating ORM's without Active Record!

Phew. Writing this all out was extremely helpful, and makes me remember that I can use my experience as an educator to benefit me as a learner. I'm starting to feel like those connections are becoming a bit more solidified and that that my brain is primed to fill in that next big hole -- How do you actually use all of this stuff to create an application that doesn't just live on your terminal and that other people can actually use???

Thursday, January 16, 2020

My First Coding Project: A Ruby CLI Application

I was a little nervous getting started with Flatiron's Coding Bootcamp. I knew that I enjoyed coding, but I had also been teaching the same introductory content for years and didn't know how I would feel once I left that little bubble. After completing my first project, I can say that it is an incredible high to have designed and written a program that uses a host of tools and concepts -- object models, interacting classes, a command line interface, data from an API -- and that someone else could legitimately use! It's a far cry from being "useful" in the real-world sense, and still leaves me with this gnawing question of "will I actually be able to create things that people are willing to pay me for?" but I do feel that this project was a big step in the positive direction. Here are some reflections on the process.

Using an API: Imposter syndrome is always an issue for me, and it is often triggered by hearing terms over and over that seem to mean something specific to everyone else but are confusing to me! Once of those terms has always been "API" -- I would hear those letters thrown around so casually and think that I must be the only one who doesn't actually understand what an API is. This project was a great way to tackle that head-on. My awesome instructors demystified the idea of an API as simply a way of communicating with a database, and modeled the process of playing with various API requests and the data they returned. I used this list of public API's to guide my research and wound up choosing Open Trivia Database's Trivia API.

Starting with the End Product: In teaching we call this "backwards planning", and I found it to be great advice in terms of getting past that blank-screen syndrome. Instead of starting by mapping out all of my classes and methods, I started high-level: by creating my command line interface. What did I want the user to see when they pressed 'run'? When they pressed 'play', what menu should appear? As I thought about this from the perspective of the user's experience, the necessity for certain methods just became super clear. (I love the idea of calling the methods you wish you had, and then going back and implementing them!) Once I had something working, I was able to go back and refactor my methods ... but it was much easier to think calmly and deeply about my program's structure when I already had some working code.

Creating Object Models: In previous labs, we had been told what attributes and methods our objects should have. This time, it was up to us to design our classes. It made sense to create TriviaQuestion objects out of the trivia question data I was retrieving. Most of my attributes corresponded with information I was pulling directly from the API request: every trivia question had a category, a difficulty, a question, etc. I had to get a bit more creative in terms of storing a TriviaQuestion's answer choices. The API returned a question's correct answer as a string, and its incorrect answers as an array of strings. For example, the following question:


has a correct_answer of "1984", and incorrect_answers of ["Catcher and the Rye", "The Old Man and the Sea", "To Kill a Mockingbird"] ... which is all well and good, but doesn't really help when the user types in 'A' or 'C' as their answer choice! So, I decided to create an answer_hash attribute in order store the (randomized) letters themselves as data, and associate them with their corresponding strings. This particular question would have an answer_hash attribute of:


The previous data structure labs were interesting but had left me wondering why and how I would organically create such a structure (like a nested hash) on my own. Hence the epiphany when I came to this part of my project!

Testing for Bugs: After some playing around, I decided that five was the right number of questions for these mini trivia quizzes. I was having a lot of fun testing my program and answering trivia questions from various categories, and it wasn't until I had done this about 15 times that I happened to hit a category that didn't have five questions for the difficulty I had specified (I believe I was looking for hard questions about gadgets?). I referred back to the API documentation and noticed that "the API appends a 'Response Code' to each API call to help tell developers what the API is doing." How convenient! As a solution, I decided that I would generate as many questions as possible using the desired category and difficulty, and then fill in the remainder with questions from random categories of the same difficulty.

In future iterations of the program, I'd like to enable the user to select multiple categories from which to pull questions. For now, I think this is an effective work-around that prevents a somewhat subtle bug from causing some unexpected behavior!

Being OK with "Smile and Nod": As I continue to embrace the beginner's mindset on my coding journey, I can't help but think about phrases I used to repeat to my high school AP Computer Science students (most of whom were coding for the first time). One such instance was on the first day of class, when they'd write their first Hello World program in Java. To get this one statement to display to the console, students would have to declare a class and a main method using public static void main(String[] args). This was literally gibberish to my kids and I tried to encourage them by telling them this was a "smile and nod" moment: although they had no idea what things like public and static meant, it didn't matter -- they could still write cool code, and if they kept on learning then one day they would understand things like public and static.

As a highly linear thinker, it can be difficult to take this particular advice myself. I like to start at the beginning and understand everything fully, in order. As we know, this just isn't always possible. One particular "smile and nod" moment for me in this project was noticing that the API calls seemed be returning strings with some encoded symbols. I did a little digging and decided that the easiest solution was to ask the API to return the data using what seemed like an even deeper level of encoding (base-64), but one that appeared straightforward to ask Ruby to decode using the Base64 module. I understand very little about encoding and literally nothing about base-64, but I'm proud that I forged ahead and patched up the problem rather than allowing it to consume me.