Meet your new friend: self

mercier_remi

Rémi Mercier

Posted on March 20, 2019

Meet your new friend: self

When coding, it's not always easy to know what's what. Why can't I call that method on this array? When in a method, what am I working with exactly? In these cases, self can prove a super-duper friend!

Even if you're a junior developer, you've probably come across self in class methods definition.

class Hello
  def self.from_the_class
    "👋 from the class method."
  end
end

Hello.from_the_class # => "👋 from the class method."

But that usually it.

Now, self can also come in handy when your understanding of what's going on is a bit muddy. Let's code something!

Say I want to recode the .map method.

  # Native method
  p [1, 2, 3].map { |integer| integer * 2 } # => [2, 4, 6]

  # Let's write our own!
  p [1, 2, 3].my_map { |integer| integer * 2 }

First thing I'll do is to start by defining .my_map and have it output something nice.

  def my_map
    "Hello from my_map"
  end

  p [1, 2, 3].my_map { |integer| integer * 2 }

What I expect, when running this code, is to get "Hello from my_map".

  NoMethodError: private method `.my_map` called for [1, 2, 3]:Array
  from (pry):8:in `<main>`

🤔 Duh.

What does it mean by private method? Let's output our current context (i.e. where the hell we are) with self.

  p "Where are in: #{self}"

  def my_map
    "Hello from my_map"
  end

  p [1, 2, 3].my_map { |integer| integer * 2 }
  "Where are in: main"
  NoMethodError: private method `my_map` called for [1, 2, 3]:Array
  from (pry):8:in `<main>`

main means that we currently are in the context of the Object object. Remember when you were told that all things in Ruby are objects? Well, even Object is an object.

Ruby top classes (or objects) are BasicObject > Kernel > Object.

BasicObject is the parent class of all classes in Ruby. Basic methods like ==, !, !=, equal? are defined there. Kernel is a module with basics public/private methods like puts, gets or chomp. Object inherits from BasicObject and mixes in the Kernel module for good measure. All other Ruby objects inherit from Object. This way, Ruby objects get tons of methods, the latter being defined at the appropriate level of abstraction. Some methods from Object include: to_s or nil?. Fancy right?

Ok, but what about that NoMethodError of mine?

Here's my mistake: I've forgotten to write .my_map in a class. So it's been defined in the Object object by default. And yet, I'm trying to call .my_map on an array.

In order to call .my_map on an array, I need to open and define it in the Array class.

class Array
  p "Where are in: #{self}"

  def my_map
    "Hello from my_map"
  end
end

p [1, 2, 3].my_map { |integer| integer * 2 }
  Where are in: Array
  Hello from my_map

👏 It works! Classes in Ruby can be opened and modified. Now that I've defined .my_map inside the Array class, I can call it on arrays. Easy peasy!

A side note: If you feel like it, you can modify the real .map and make it do weird things too.

Alright, now I want to pass the { |integer| integer * 2 } block to .my_map. I know I should loop through the array and yield the block at some point. But since I'm not passing the array as an argument, where am I to call .each on it?

Let's see what self has to say.

class Array
  def my_map
    # Instantiate an empty array to store results
    results = []
    # Output yield to see the current context
    "Where are in: #{self}"
  end
end

p [1, 2, 3].my_map { |integer| integer * 2 }
  "Where are in: [1, 2, 3]"

When we are in .my_map, the default value we're working with is the array .my_map was called upon. I now know I can call .each on self (i.e. [1, 2, 3]).

class Array
  def my_map
    results = []
    # Loop through the array and output yield just to see what's what
    self.each do |i|
      yield
    end
  end
end

p [1, 2, 3].my_map { |integer| integer * 2 }
  NoMethodError: undefined method `*' for nil:NilClass
  from (pry):94:in `block in <class:Array>'

First, I can read from the error message that I'm in the <class:Array>. That's good. Now, what about this NoMethodError: undefined method '*' for nil:NilClass? Well, it simply says that in the NilClass 1, there are no methods * defined 2.

It means that my block { |integer| integer * 2 } can't execute the multiplication because the integer variable inside it is nil. yield can take arguments though. So inside the loop, I'll just pass the current integer - i - to yield.

class Array
  def my_map
    results = []
    # Loop through the array and execute the block
    self.each do |i|
      # Give yield the current integer and store result in array
      results << yield(i)
    end
    # Return final array
    results
  end
end

p [1, 2, 3].my_map { |integer| integer * 2 }
  [2, 4, 6]

Which can be refactored like this:

class Array
  def my_map
    results = []
    # Loop through the array and execute the block
    each { |i| results << yield(i) } # self is implicite so I can remove it
    results
  end
end

p [1, 2, 3].my_map { |integer| integer * 2 }

🥳 Done!

self is also pretty useful to figure out the scopes of your variables. But that'll do for another article.

The key takeaway for today is: Next time you don't know where the fuck you are in your code, call self to the rescue!

Cheers,

Rémi


  1. Yes, nil also has its own class. 

  2. Yes, * is a method defined on specific objects 🤪. To check if NilClass has the * method defined, run NilClass.public_methods.include? '*' and NilClass.private_methods.include? '*' in irb

💖 💪 🙅 🚩
mercier_remi
Rémi Mercier

Posted on March 20, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Meet your new friend: self
ruby Meet your new friend: self

March 20, 2019