Let’s go through something simple and something complicated.
Do you want the simple or the complicated first? I guess you actually don’t get a choice, attr_reader; I make the decisions here, so let’s start with the simple. (read the following paragraph in your best late night TV ad presenter voice.)
Have YOU ever tried adding ASCII art or letters to your code, only to find out that it DIDN’T look the same when you ran your program?! You, my friend, are Not. The. Only. One!
I bet you’ve got some backslashes in there, and I bet you’re doing a simple “puts” statement inside an innocuous double quotation mark. A little “”. It’s not harming anyone. But I bet it’s returning something like this:
The reason for this is because your simple, useful double quotations support “escape characters”. Basically, your backslash alone simply disappears inside a string encapsulated by double quotation marks, while your backslash with meaningful characters can perform useful functions in your code; and you’ll never see them as your program runs. In fact, I use one of these escape characters in the method we’ll dive into next.
— — — — — — — — — — — — — — — — — — — — — — — — — —
So let’s go ahead and take that method apart, piece by piece, for a return value of understanding instead of frustration.
We’ll begin by simply defining a method. It’s so easy! The rest of this method will be a piece of cake! (She says, before staring at a computer screen aimlessly.)
Beautiful. We have some text in another method, let’s go ahead and call that into this one, we’ll also define a local variable while we’re at it because hey — we’re all coders here! We know how to code. This stuff’s easy.
def display_description self.display_description_text user_input = gets.chompend
Oh, sorry, you were asking about self? We don’t actually need that, because if we just called in display_description_text, Ruby would just KNOW that it’s an instance method. Ruby is So. Smart. But it’s good to go ahead and let other people know it’s an instance method when they’re just glancing over our code, you know? And it’ll help you (me) remember too!
“What’s that again?” Oh yeah, that’s an instance method. Simply put, we couldn’t call this method on the class itself, but we could call it on a new INSTANCE of the class. And since #display_description is inside the class body, we can just call this method, #self.display_description_text, here.
Let’s move on to gets.chomp — — — — — — — — — — — — — — — —
The gets method is exactly what it sounds like — it gets input from the user that it then turns into a string for us.
But here’s the thing: gets interprets the user’s “enter” as a new line. What’s the deal with newlines?!
We don’t want them. Chomp gets rid of newlines for us. Again, easy peasy lemon difficult. Let’s add some more stuff to this method!
def display_description self.display_description_text user_input = gets.chomp if user_input.to_i == 0 book = Book.find_by_name(user_input.split.map(&:capitalize).join(‘ ‘))end
“Excuse me? We were going over stuff that looked simple! It was nice, it was easy, it made sense to read, it was fun. How did we get here?!”
We got here, simply put, because I wanted the user to be able to enter an index number OR a title, and I didn’t want them to HAVE to enter it exactly. What if they didn’t want to type any capital letters? I am not unsympathetic. I wanted to have every option available. So here I am. A girl, standing in front of a block of code, asking it to accept both strings and integers.
So first we have to tell the program to make user_input (the string we got from gets, you remember?) into an integer. Thankfully there’s a pretty simply method for that — .to_i
Okay, but what happens if I type in a string and call .to_i on it? Like if we put in “Elizabeth”.to_i? What’s that return value?
So then what we do with that is pretty simple. We tell the program that if a user’s input, as an integer, is equal to 0 (this is what those double equal signs are for), then we’ll assign that local variable “book” to the string they entered, using a different method called #find_by_name. Since we don’t want to box the user into using exact formatting, we also have some crazy iteration going on there.
Let’s do this fast — .split cuts up the string entered (the first letter of every word in the book title is capitalized), .map makes changes to every string passed through it and (&:capitalize) is what we want to do to those individual strings (not to be confused with .uppercase, which would change every letter to uppercase), lastly we want to rejoin those strings to make “Each Book Title” a single string instead of looking for “Each” “Book” “Title” inside the array where we have saved all our new instances of the Book class.
“Whew. Are we done yet? No?”
def display_description self.display_description_text user_input = gets.chomp if user_input.to_i == 0 book = Book.find_by_name(user_input.split.map(&:capitalize).join(‘ ‘)) if book == nil puts “\nPlease type the name exactly, or use the appropriate number.” self.display_description else puts book.description endend
Okay, okay. Luckily for us, what’s next is a big chunk of the code, and actually pretty fast to talk about. But it does result in a nested if statement.
“A what?” Sings the chorus…
I have if statements All. Over. this program, telling it to do one thing or another based on what the user entered. Now, when I was ONLY checking for valid strings (before I added in the option to use the index number to choose a book) this was pretty simple. The user either entered a valid string, resulting in a description of the book being output, or they didn’t, resulting in an error message. But then I added in .to_i and gave myself a headache. See, because now I have this spot where the code says:
“Hey, it looks like the user entered a string, because the return value of that input is 0! Let’s go grab a book!”
Then if the code can’t find a book, like a petulant child it just…kind of…throws a tantrum.
Okay, actually it just exits.
And it causes me a headache.
So here, where we’re grabbing that book for the user based on the title they entered, we have to put another if statement. We have to say “If the string they entered is nil, then use your words, little program, and tell them to enter something valid!”. Because if a string is entered that DOESN’T exist in our saved list of books, the return value is going to be nil.
So we tell the user to enter something valid and restart the loop. But let’s say that the user does enter something valid; then else kicks in, and outputs the description of the book they chose.
And look, the end is in sight! But you didn’t think we were done, did you?
def display_description self.display_description_text user_input = gets.chomp if user_input.to_i == 0 book = Book.find_by_name(user_input.split.map(&:capitalize).join(‘ ‘)) if book == nil puts “\nPlease type the name exactly, or use the appropriate number.” self.display_description else puts book.description end elsif (1..Book.all.length).include?(user_input.to_i) book = Book.all[user_input.to_i — 1] puts book.description else puts “\nThe option you have chosen does not seem to exist in this universe, please try again.” sleep 1 self.display_description endend
We’re getting there though. Now we have an elsif.
“Wait, but didn’t we end the loop?” The chorus chimes in, again.
We ended that nested if statement. Isn’t this block of code Just. So. Fun? It really zigged whenever I thought it would zag.
We’re to the integers though! The light at the end of the tunnel! The ease with which we can just choose based on an index! We define our index, starting at 1(where humans usually begin an index), and end at the Book.all arrays length. Aren’t these built in methods so easy and fun? And here we’ll say to the program “If the user has entered an integer (user_input.to_i) included in this index, then please assign our local variable “book” to the object located at the index they chose”.
Plainly speaking, we look through Book.all (an array) and pick out an object at a specific index using brackets, and then inside those brackets we put in our local variable (whatever the user entered, minus one, because an array’s index starts at 0). And then if they entered an integer that was included in our index, we output the appropriate book description.
If Anything. Else. happens, at this point, then it is not going to be valid, because we have combed over every option that is valid.
So at this point, if the user has entered “dhvkjdfbjkae” (which I feel confident in saying is NOT the name of a book), then we will output an error message for them Using. Our. Escape. Character! (to bring us to a new line), and we start the whole loop over again. And we only exit the loop once a valid option is chosen or we control+c the heck outta there.
— — — — — — — — — — — — — — — — — — — — — — — — —
Thank you, dear reader, for getting through that! I know it’s highly specific to my program, but hopefully some ideas were broken down well enough to easily understand how to deal with situations like this.