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