DEV Community

Cover image for Get to Know Your Self (Refactored)
Meg Gutshall
Meg Gutshall

Posted on • Edited on • Originally published at meghangutshall.com

Get to Know Your Self (Refactored)

While Ruby is an incredibly user-friendly language, it’s not without its conundrums—one in particular: self. self is a Ruby reserved word, or a word specifically designed to have a special meaning in the Ruby language. The scope (or program visibility) of self is any class or instance of a class, which we can also refer to as an object. This means that self can enable developers to contextually reference a particular class or instance of a class—depending on what the needs are for their program—without using a specific variable name.

If the method is a message, who is the receiver? The object! Many times the object and self are one in the same, but sometimes they aren't. In the example below, I'll explain how self changes depending on the "message" that's being sent.

class AfroPet
  attr_accessor :species
  @@all = []

  def initialize(species)
    @species = species
    @@all << self
  end

  def self.all
    @@all
  end
end

hipmo = AfroPet.new("Hippopotamouse")
eleham = AfroPet.new("Eleham")
ghound = AfroPet.new("Giraffet Hound")
Enter fullscreen mode Exit fullscreen mode

The code above shows the AfroPet class which has a species attribute as well as an @@all class variable. Two methods have been defined as part of the AfroPet class: #initialize and self.all. In addition, three new species of AfroPet are created, including the pocket-sized Eleham!

Elephant shrew in the Berlin Zoo

self Referring to an Instance of a Class

We first see self being shoveled into the @@all class variable on Line 7, in the #initialize method. At this moment in the program, what is self referring to?

Remember that whenever we instantiate a new object (also referred to as creating a new instance of a class), we call #new on the class itself, then immediately call #initialize on the object just created—like a one, two punch. By writing @@all << self into our #initialize method, we enable the new instance to be automatically saved into the @@all class variable at the time of creation instead of having to manually add it later, which saves us time and keeps our code DRY!

To refer back to my message analogy, in this example #new is the sender while the #initialize method is the message. Furthermore, since we find self being utilized inside the #initialize method, it is considered part of what is called the method scope, confirming that self does indeed refer to the newly created object.

self Referring to a Class

We next see self on Line 10 in the self.all class method. This is a class method designed to return the value of the @@all class variable, which is an array of instances of the AfroPet class. As you saw above, we created three new instances of the AfroPet class: hipmo, eleham, and ghound. So what happens when we call the self.all class method?

AfroPet.all
#=> [#<AfroPet:0x000055b7db32c9c0 @species="Hippopotamouse">,
     #<AfroPet:0x000055b7db32c970 @species="Eleham">,
     #<AfroPet:0x000055b7db32c920 @species="Giraffet Hound">]
Enter fullscreen mode Exit fullscreen mode

After typing AfroPet.all in our terminal, it returns an array containing our three previously-instantiated AfroPet objects, showing that the program recognizes that in this case, self is referring to the AfroPet class. We know this because the recipient of this particular "message" (or any class method as denoted with the self. prefix before the method name) is the class itself, which means that self is not scoped to any particular instance, but to the class instead.

So how does self know when to change the object it’s referencing? Two ways:

  1. The first being context as we have previous discussed above.
  2. The second being a built-in rule for this reserved word: one and only one self will exist at any given time in the program. There will never be a case in which self will refer to more than one object at a time, therefore leaving it up to context to determine what self actually means at any given point in the program.

I know, that’s a lot of information and your head is probably spinning by now, but let’s do one more example of self. I’ll walk you through it!

class Creamery
  attr_accessor :creamery_name, :ice_cream_flavor, :creamery_flavors
  @@all = []

  def initialize(creamery_name, ice_cream_flavor)
    @creamery_name = creamery_name
    @ice_cream_flavor = ice_cream_flavor
    @creamery_flavors = []
    self.creamery_flavors << @ice_cream_flavor
    @@all << self
  end

  def new_flavor(ice_cream_flavor)
    @ice_cream_flavor = ice_cream_flavor
    self.creamery_flavors << @ice_cream_flavor
  end

  def self.all_creamery_flavors
    @@all.each do |creamery|
      creamery.creamery_flavors.each do |flavor|
        puts "#{creamery.creamery_name} " + flavor
      end
    end
  end
end

bnj = Creamery.new("Ben and Jerry's", "Chunky Monkey")
hersheys = Creamery.new("Hershey's", "Chocolate")
nelsons = Creamery.new("Nelson's", "Graham Slam")
Enter fullscreen mode Exit fullscreen mode

After defining the Creamery class, we create three new creameries: bnj, hersheys, and nelsons.

In this example, the #initialize method creates a new creamery given the creamery_name and ice_cream_flavor attributes. Here we also declare an empty array called @creamery_flavors to store this creamery’s ice cream flavors. The ice_cream_flavor is added to the creamery’s @creamery_flavors instance variable and the creamery itself is added to the @@all class variable. Remember: In the last example, we saw how self refers to the newly created object when used inside the #initialize method.

You can add a new flavor to a creamery by calling #new_flavor on that instance of creamery and declaring the new ice_cream_flavor as a String argument of the method. The new flavor is then added to the creamery’s @creamery_flavors array. Does the context in which self is used here look familiar? (Hint: See the #initialize method!)

hersheys.new_flavor("Vanilla")
#=> ["Chocolate", "Vanilla"]
hersheys.new_flavor("Strawberry")
#=> ["Chocolate", "Vanilla", "Strawberry"]

bnj.new_flavor("Chubby Hubby")
#=> ["Chunky Monkey", "Chubby Hubby"]
bnj.new_flavor("Oat of this Swirled")
#=> ["Chunky Monkey", "Chubby Hubby", "Oat of this Swirled"]
Enter fullscreen mode Exit fullscreen mode

Lastly, the self.all_creamery_flavors class method iterates over the creameries in the @@all class variable and displays all the ice cream flavors contained within the Creamery class. It works like this:

  1. Iterate over the @@all class variable to access each creamery instance.
  2. For each creamery, call #creamery_flavors to access their instance variable array of ice_cream_flavor attributes.
  3. Chain the #each method on that to iterate again over this nested array, this time puts-ing out the flavors to the terminal.

The original @@all class variable will automatically return at the end of this method's execution.

Creamery.all_creamery_flavors

"Ben and Jerry's Chunky Monkey"
"Ben and Jerry's Oat of this Swirled"
"Ben and Jerry's Chubby Hubby"
"Hershey's Chocolate"
"Hershey's Vanilla"
"Hershey's Strawberry"
"Nelson's Graham Slam"

#=> [#<Creamery:0x00005558bceb7e08 @creamery_name="Ben and Jerry's", @ice_cream_flavor="Oat of this Swirled", @creamery_flavors=["Chunky Monkey", "Chubby Hubby", "Oat of this Swirled"]>,
     #<Creamery:0x00005558bceb7d68 @creamery_name="Hershey's", @ice_cream_flavor="Strawberry", @creamery_flavors=["Chocolate", "Vanilla", "Strawberry"]>,
     #<Creamery:0x00005558bceb7cc8 @creamery_name="Nelson's", @ice_cream_flavor="Graham Slam", @creamery_flavors=["Graham Slam"]>]
Enter fullscreen mode Exit fullscreen mode

You just learned self! If you feel you have a good grasp of the concept of self, celebrate with a scoop of your favorite ice cream! If not, check out the following helpful resources:

Ruby self
Self in Ruby: A Comprehensive Overview
Understanding self in Ruby

...And then have a scoop of ice cream. :D

Refactor Bonus Section

Over a year after I originally wrote this blog post, I had occasion to come back and review my old posts. My God this one was not pretty! I tried to clean it up a bit and use better examples/explain them better. If you have any comments to add or see something that's glaringly wrong, please DM me on Twitter at @meg_gutshall or email me at meghan.gutshall@gmail.com.

In my second round of research, I found a remarkably interesting and high-level post on self in Ruby and wanted to share it as bonus content. Warning: This is confusing shit and I am still far away from feeling completely comfortable with it. If you're a beginner, I highly recommend you stick to the three links above and bookmark this one to revisit at a later date: Metaprogramming in Ruby: It's All About the Self.

Also, I rewrote both of my code examples used above for this refactored blog post and wanted to show a method for the second example that could be of general use—it's just not necessarily related to self.

class Creamery

  ...

  def all_flavors
    @creamery_flavors.each {|flavor| puts flavor}
  end

  def self.all_creamery_flavors
    @@all.each {|creamery| creamery.all_flavors}
  end
end
Enter fullscreen mode Exit fullscreen mode

You may have noticed that we had to iterate twice in the blog post's self.all_creamery_flavors method. Here I created an #all_flavors instance method that iterates over the @creamery_flavors array, puts-ing each flavor just like the blog post example. The only difference is, this method is scoped to an individual creamery. Then, I refactored the self.all_creamery_flavors class method to call on the new #all_flavors instance method once it goes through its first iteration in accessing individual creamery instances.

Top comments (4)

Collapse
 
andy profile image
Andy Zhao (he/him)

Love the self.all method! It's like self-made (get it?) version of the Rails Model.all. Makes me wonder what the Rails implementation is like.

Collapse
 
meg_gutshall profile image
Meg Gutshall

Hello Andy! I just realized I didn't reply to this comment when I first published this post and I apologize for that. Since it's over a year later and I punched the post up a bit I'm curious to know:
1) What do you think of the "refactored" post?
2) What have you learned about self since your last comment?

Please let me know if I have anything wrong or if there's anything you think I should add to this post!

Collapse
 
ben profile image
Ben Halpern

For some reason I’ve always felt self to be quite intuitive even though I logically agree with all the weird things.

I always had way more hangups with, say, JS’s this.

Collapse
 
meg_gutshall profile image
Meg Gutshall

I was just thinking about that today! I have very basic knowledge of JS and remember learning about this and not getting it at all. I'm hoping my understanding of Ruby's self with help when I get to JS's this in the Flatiron curriculum.