Finding out what called a Ruby method
A quick way to understand what is calling your code using the caller method
Sometimes when we’re debugging some Ruby code, it is good to know what code has called the method we’re looking at. It is something I routinely like to know.
There are a few different ways we can go about this, but the one I reach for more often than not is the Ruby kernel method caller
.
From the above-linked documentation, caller
;
Returns the current execution stack—an array containing strings in the form file:line or file:line: in
method
.
I like it because;
- It is built into Ruby, so no additional gems needed
- It is easy to learn how to use it
I think the best way to talk about this method is with an example.
require 'date'
class Cat
def about
puts "name: #{name}"
puts "age: #{age}"
end
private
def name
'Pixel'
end
def age
DateOfBirth.find_age(Date.new(2020,02,03))
end
end
class DateOfBirth
def self.find_age(date)
((DateTime.now - date) / 365).to_i
end
end
puts Cat.new.about
Here we have two classes, Cat
which has some information about cats, and DateOfBirth
which we’re using to try and calculate an age based on a date of birth.
Don’t worry too much about the specific code, and please please please don’t use this code to work out age based on date of birth.
In this code we’re calling Cat.new.about
and we want to see the cat’s name and age returned to us.
The developer here has been quite lazy and has hardcoded everything, so the Cat
class is always going to make a cat with a specific name and date of birth.
Let’s pretend we don’t know that, lets assume this is a massive project and Cat
isn’t something we’ve ever had to look at.
What we’re looking at is the DateOfBirth
class, there has been a bug report that no matter what Cat
we’re dealing with, their age is always the same, and we’ve been told the find_age
method is probably to blame.
We open up our code editor and look at the relevant code, copied below for ease;
class DateOfBirth
def self.find_age(date)
((DateTime.now - date) / 365).to_i
end
end
We can see that find_age
is maybe being overly simplistic with how it calculates the age, since not every year has 365 days, but it doesn’t explain our bug.
Enter caller
If we update the find_age
method to include puts caller
and run our Cat.new.about
we get the following output;
name: Pixel
caller.rb:16:in `age'
caller.rb:6:in `about'
caller.rb:27:in `<main>'
age: 4
The “name” and “age” lines are from our Cat
code, we can ignore them, the important bits are the “caller.rb” lines.
From bottom to top, we can read this as all of the code executed on the way to our method, if we lay it out from bottom to top, we get;
- caller.rb:27:in `<main>’
- caller.rb:6:in `about’
- caller.rb:16:in `age’
To break this down;
caller.rb
is the name of the Ruby file:x
, eg:6
is the line number in that file:in 'method'
, eg:in 'about'
is the method that the line resides in
Then in our specific case, we’re saying three things happened.
<main>
was called, which is ourCat.new.about
codeCat.new.about
then went to line 6 in theabout
method, which callsage
- Line 16 in our
age
method then calls ourDateOfBirth.find_age
method
Now we have something to go on, we know the caller of our code, so we can open up caller.rb
and check on line 16, and sure enough we will see it is hard-coded and is the source of our bug! Huzzah!
Improving on caller
In our little example it is fine to call caller
, there was never going to be that much output, but in a real application, especially one that uses a framework, these stacks get get pretty large.
caller[0]
If you just want to care about the very last thing to run before our method, you can use puts caller[0]
, in our case it would have returned caller.rb:16:in 'age'
which is exactly what we needed.
caller(x, y)
You can also tell caller
where you want to start from and how many lines to return.
For example puts caller(0, 2)
will return from the last line which called our method plus one more, for a total of 2 lines. This can be useful when you want more context, but don’t want too many lines to get returned. I like to start with 5 and see if I need to tweak from there.
Prefer a video?
I’ve recorded a short video explaining this article and added it to YouTube. I know some folk much prefer to watch and listen than read!