by Dan DeMeyere - @dandemeyere
If you're not interested in learning about Ruby, this post may not be valuable to you. Proceed at your own dorky will.
Super quick backstory: Ever since joining thredUP back in early September this year, I've been new to Ruby. I spent my first couple of months learning our app and gaining a functional knowledge of Ruby on Rails. 9 months later, it has become time for me to go back and back-fill the fundamentals of Ruby that I missed.
In my opinion, having sound fundamentals in anything, from Ruby to basketball, is what allows you to consistently produce quality results. Luckily for me, thredUP schedules in a healthy amount of time to be designated for professional development. This post is the first in a series of many that will catalogue what I previously did not know or were ambiguous at how they worked behind the scenes.
Please take a look at this example I wrote:
This is mock class that will demonstrate how using self in Ruby works in the context of instance variables. If you read through this class, you'll notice it's pretty straight-forward. Take a minute and write down what you think will be outputted when lines 28-36 run and don't cheat by looking at the bottom of this post.
Don't worry, I'll wait.
Once you're done, compare your results to the code at the bottom of the post. If you were anything like me an hour ago, you would have made some wrong assumptions. So let's figure out why. First, let's define some vocabulary.
self - this refers to the current object in Ruby.
- explicit receivers - the object you are setting to be invoked upon in Ruby.
- instance variables - commonly denoted with the '@' sign such as @variable. These types of variables are not public (their scope is confined to self), but they are global (broader scope accessibility than standard variables).
That might not seem so tricky, but if you're not careful when using instance variables then you can find yourself accessing or overwriting the wrong variables. So let's dive in head first. On line 28, when we print self out to the screen, the output will show the class name Dolphin. Simple enough - we're in class Dolphin when it's called so there's no surprise there. When we output @display, you'll notice 'no dolphin specified' is printed out. Since that's its original value set on line 2 and no other call has been made yet, this shouldn't be a surprise either.
Next we call Dolphin.bottlenose on line 31 to build our new Dolphin object, which we set to a standard variable named dolphin. Here's where it gets tricky. When we invoke the display method on our dolphin object, we are setting dolphin as the explicit receiver, which will change the object that self is pointing to and therefore the scope of @display changes. You'll notice that the first time we output @display from within the display method, the value is blank or nil. Weird, huh? It's because @display in the context of your dolphin object (i.e. #<Dolphin:0x1001370d0>) is different then in the context of Dolphin. The simple distinction between what the current explicit receiver is set to determines this. If it's still a little confusing, read on as I'll clear it up.
After we finishing outputting within the display method, @display now holds a new string value when the dolphin object is the explicit receiver. Line 34 will demonstrate the difference as it will once again print 'no dolphin specified' since the self object is now referencing the class Dolphin again.
So why is this useful? Well, take a look at what line 36 outputted. You'll noticed that even though @display was not set within the color method, we're still able to access the contents of the instance variable that was set within the display method since the color method was invoked on our individual dolphin object, which changed the context of self again.
Hopefully this was as helpful to you as it was to me for learning how to differentiate how instance variables are set, accessed and scoped. Here is the source code with the output directly following it to make it easier to read what's going on: