Severin Perez

Knowing One's Self (in Ruby)

March 03, 2018

In Ruby, as in life, knowing one’s self is a vital element for long-term success. Let’s take a minute to explore self and why it matters.

The term self is aptly named, for you can think of it as a Ruby program asking, “who am I right now?” The answer to this question is important because it determines which behaviors and attributes are available to the program at any given time. Therefore, an attempt to access a behavior (that is, a method) in one place in the program might fail, whereas in another it will succeed. Ultimately, this is a question of context—Ruby is using the concept of self to determine which execution context to use at any point in the program.

But what does this mean in practice? First, a technical definition. The Ruby documentation defines self as the “current object”, which is to say, the object currently being used as the execution context. Since Ruby is an object-oriented language, all program execution is done in the context of some object (even top-level methods, which at first glance don’t appear to be attached to any particular object, but actually are.) We’ll talk more about the current object shortly, but the most important thing to take away is that there may only be one current object at any given time. Imagine the bugs that would arise if your Ruby program had an identity crisis and didn’t know who it was!

In real life, the self might simultaneously be composed of many parts. You can be an employee, a husband or wife, a son or daughter, a friend, and an athlete all at once (and more!) In Ruby, your program can only be one of these things at any given time. Let’s imagine a world where people are the same way. Perhaps it would look something like this:

class Employee
  def greeting
    puts "Good morning boss!"
  end
end

class Spouse
  def greeting
    puts "Hi honey!"
  end
end

Here, each class has a method called greeting, which defines how an object of that class might greet someone in a role-appropriate context. Imagine you are in the office and your boss walks in, it would be odd to say “Hi honey!” wouldn’t it? At that point in time you are in the context of an Employee, so you say “Good morning boss!” instead. Now imagine your spouse calls. You see on caller-id who it is so you switch context to that of Spouse and pick up the phone and say “Hi honey!” Ruby is doing the same thing. Throughout a program it is reassessing context, changing its understanding of self, and using that information to decide which behavior to use. The difference however with real life is that while Ruby is in one context, it isn’t capable of using behavior from another context. (Whereas you could in theory say “Hi Honey!” to your boss if you wanted to.) This is at the root of why self is an important concept.


OK, let’s get into the weeds a bit. If self is so important, what is it at different times? There are four main areas where self changes, and we will talk about each.

The first area is top-level execution. The top-level of your program is the part that is outside of any class, module, or method definitions. Think of it as naked code that has not yet been wrapped in anything else. Remember earlier when I mentioned that all execution in Ruby occurs in the context of some object? Well, that’s true at the top-level too. Here, the default object (or current object if you prefer) is a special object called main, which is itself an instance of the Object class. Check this out:

def top_level_inspect_*self*
  p *self*
end

top_level_inspect_*self*   # main
p Object.private_methods.include?(:top_level_inspect_*self*)   # true

Here, we have a top-level method called top_level_inspect_self, which simply inspects the current self object. A call to this method from the top level outputs main, which is to say that at the top level, the default object is this mysterious thing called main. Interestingly, if we check whether the private methods defined on the Object class includes one called :top_level_inspect_self, we get a response of true. This is because top-level methods are actually assigned as private methods on the Object class (but that’s a topic for another post.) Crucially though, we see that making a call to the top_level_inspect_self method in bareword style (that is, with no explicit receiver), from the top level itself, uses self as the implicit execution context, thus outputting main. More on that shortly.

Let’s look at the next two areas where self changes: class and module definitions.

module MyModule
  def *self*.module_inspect_*self*
    p *self*
  end
end

class MyClass
  def instance_inspect_*self*
    p *self*
  end
  
  def *self*.class_inspect_*self*
    p *self*
  end
end

MyClass.class_inspect_*self*    # MyClass
MyModule.module_inspect_*self*  # MyModule

Here, we have a module, MyModule, and a class, MyClass, both of which have class methods that report the results of inspecting the current self. Calls to these methods return MyClass and MyModule respectively. That is, when these objects (note that classes and modules in Ruby are in fact types of objects) inspect their context, they see, well, themselves! This demonstrates how, within class and module definitions the current object is set to the class or module itself. We can actually see this illustrated another way. See how the class method definitions start with def.self? This tells the class that the method being defined is not for use by any created instances of that class, but by the class itself.

At the risk of being verbose, let’s look at this one more way.

class MyClass
  def instance_inspect_*self*
    p *self*
  end
  
  def *self*.class_inspect_*self*
    p *self*
  end
  
  def *self*.announce_and_inspect_*self*
    puts "Reporting for inspection!"
    class_inspect_*self*
  end
end

MyClass.announce_and_inspect_*self*
#  Reporting for inspection!
#  MyClass

Now, MyClass has another class method called announce_and_inspect_self, which outputs a quick introduction and then calls our original method, class_inspect_self. As expected, the call to this method outputs MyClass again. But notice how the method was called in bareword style (with no explicit receiver), and yet, Ruby still gave us the same output as before. This is because self was set to MyClass and an explicit receiver was therefore unnecessary. This is one of the principle benefits of calling code in a given context—Ruby knows where to look for the right method, and does so in a predictable fashion.

As you may have noticed, I snuck an instance method into our earlier code as well. Let’s see what happens when we instantiate an object of the MyClass class and call that method.

my_instance = MyClass.new
my_instance.instance_inspect_*self*  # <MyClass:0x00000000a88dd8>

When an inspection is carried out on self from within this instance method, you see that we are now getting an output of the calling object rather than MyClass. This is the next area where self changes. Inside an instance method self is the object that is calling the method. We’ll save an exploration of how objects take advantage of this behavior for another time. What you should remember for now is that self in instantiated objects differs from self within the object’s class.


So why does all of this matter? Put simply, it matters because in larger programs you are likely to encounter situations where behavior changes depending on context. We could very well have named all of our previously used methods simply inspect_self and would have gotten the same results. Ruby would have used its understanding of self, that is, its understanding of the current execution context, and known when to use which method. For the sake of illustration, we used very specific names like class_inspect_self, but in a real-world program that would have been clunky and unnecessary. Our understanding of context lets us know which methods are being called where, even if they share a name. There are other situations where context matters as well, such as the resolution of instance variables. These are just a few reasons why it is important that you should always know what self is at any given point in a program.

To wrap-up, let’s end with a few key takeaways.

  • self is the default object, aka current object, which defines the execution context of a Ruby program at any given point in time.
  • There is one and only one object filling the role of self at any time. self changes, depending on whether it is used at the top level, in a class or module definition, or in an object instance method.
  • At the top level, self is a special object called main, which serves as the default execution context.
  • In class and module definitions, self is the class or module itself.
  • In instance methods, self is the object that is calling the method.
  • The object filling the role of self has special privileges, most notably, that it acts as the default receiver for methods called in bareword style.

I hope this has been a useful introduction to Ruby’s concept of self. And it’s just the beginning! As you explore object-oriented programming you will find that self has a few quirks (note especially how self is used a bit differently in setter methods within objects), but as long as you know self, and always remember what it is and when, you’ll be just fine.


Note: This article was originally published on Medium.


You might enjoy...


© Severin Perez, 2021