Finding where a method is defined in Ruby using IRB
Tracing method definitions in Ruby via IRB: Clear and concise examples
After many years in Ruby, I am still fascinated by the flexibility, simplicity, and power of IRB
and rails console
. Even with IDEs getting better, I like the experience of using IRB or rails console to explore the code.
In this article, I will show how to find the source where a method is defined.
There are three ways to find out where a method is defined in Ruby using IRB:
using the
source_location
defined on the Method objectusing the IRB
show_source
commanddisplaying information about an object using
ls
(for displaying the object but not the source file)
Before we start, here is my IRB configuration:
# .irbrc content
# I use the Dracula theme for IRB from https://github.com/katsyoshi/irb-theme-dracula
require "irb/theme/dracula/light"
All code executed below is run inside irb
.
A simple Ruby script
Let's assume the following object:
# frozen_string_literal: true
module Universe
module Galaxy
class Star
attr_reader :name
attr_writer :name
def initialize(name)
@name = name
end
end
end
end
Now let's start IRB and instantiate an object out of it:
require_relative "universe/galaxy/star"
star = Universe::Galaxy::Star.new("Some Other Sun")
Finding available methods and instance variables
When in IRB you can use ls
to display the available methods and instance variables:
ls star
# =>
# Universe::Galaxy::Star#methods: name name=
# instance variables: @name
While this does not provide a source file and a line number you will see in examples below that it can correctly identify in case of inheritance for example where a method is defined.
Finding the source using Method#source_location
Executing star.method(:name).source_location
will return the proper source location with the specific line number:
star.method(:name) # This will return a Method object
star.method(:name).source_location
# =>
# ["universe/galaxy/star.rb", 6]
What about the setter? It will work in this case too:
star.method(:name=).source_location
# =>
# ["universe/galaxy/star.rb", 7]
Finding the source using show_source
from IRB
When in IRB you can use show_source
to display the source for a method:
show_source star.name
The response will be:
From: universe/galaxy/star.rb:6
attr_reader :name
attr_writer :name
This will work for the setter too so executing the following command:
show_source star.name=
Will return the following response:
From: universe/galaxy/star.rb:7
attr_writer :name
A more complex Ruby script
What if we add inheritance? How will discovering the source work in this case?
Let's now create a new class:
module Universe
module Galaxy
class SimpleSun < Star
def initialize
super('Sun')
end
def our_planet
Planet.new('Earth')
end
def name
puts "SimpleSun#{@name}"
end
end
end
end
Let's load this in an IRB session:
require_relative "universe/galaxy/simple_sun"
simple_sun = Universe::Galaxy::SimpleSun.new
Finding available methods and instance variables
Let's explore with ls
:
ls simple_sun
# =>
# Universe::Galaxy::SimpleSun#methods: name our_planet
# Universe::Galaxy::Star#methods: name=
# instance variables: @name
Notice that it correctly showed that name
is from Star
and was not overwritten in the SimpleSun while name=
is correctly identified as being from Universe::Galaxy::Star
Finding source location with Method#source_location
Using source_location
on name
will show the source being in simple_sun.rb
while using source_location
on name=
will show the source being star.rb
:
simple_sun.method(:name).source_location
# =>
# ["universe/galaxy/simple_sun.rb", 14]
simple_sun.method(:name=).source_location
# =>
# ["universe/galaxy/star.rb", 7]
Finding source location with show_source
IRB command
Using show_source
will correctly display the source code for both name
and for name=
(inherited from Star
).
Executing:
show_source simple_sun.name
Will return the following response:
From: universe/galaxy/simple_sun.rb:14
def name
puts "SimpleSun#{@name}"
end
While executing:
show_source simple_sun.name=
Will return the following response:
From: universe/galaxy/star.rb:7
attr_writer :name
An extra option for show_source
Let's execute again the show_source
but this time on the initializer where in SimpleSun
I wrote super('Sun')
so I am calling super with an argument:
show_source Universe::Galaxy::SimpleSun#initialize
This will return:
From: universe/galaxy/simple_sun.rb:6
def initialize
super('Sun')
end
What if you want to see the super
method? show_source
supports a flag -s
that will help displaying the super class method. You can stack as many -s
as you like eg. -sssss
In our case there is one single super added there so I will execute:
show_source Universe::Galaxy::SimpleSun#initialize -s
And this will return:
From: universe/galaxy/star.rb:9
def initialize(name)
@name = name
end
A Ruby script using define_method
Now let's complicate a bit the things and define methods dynamically with define_method
:
require_relative "star"
module Universe
module Galaxy
class TwinSun < Star
NAME_ALTERNATIVES = %i(twin_helios twin_sun twin_sol twin_solis)
def initialize
super('TwinSun')
end
def our_planet
'Twin Earth'
end
NAME_ALTERNATIVES.each do |name|
define_method "#{name}?" do
true
end
define_method name do
name
end
end
end
end
end
With our new class defined, let's start the IRB and instantiate twin_sun
:
require_relative "universe/galaxy/twin_sun"
twin_sun = Universe::Galaxy::TwinSun.new
Finding available methods and instance variables
And we can explore with ls
:
ls twin_sun
# =>
# Universe::Galaxy::TwinSun#methods:
# our_planet twin_helios twin_helios? twin_sol twin_sol? twin_solis twin_solis? twin_sun twin_sun?
# Universe::Galaxy::Star#methods: name name=
# instance variables: @name
You can notice that it shows all the defined method too when doing ls twin_sun
.
ls
also correctly identifies that name
and name=
are from the parent Star
object.
Finding source location with Method#source_location
What about source_location
?
Let's first try a method defined directly on the TwinSun
and notice it shows the file and the line number:
twin_sun.method(:our_planet).source_location
# =>
# ["universe/galaxy/twin_sun.rb", 12]
Now let's try to identify one of the methods created with define_method
:
twin_sun.method(:twin_sun?).source_location
# =>
# ["universe/galaxy/twin_sun.rb", 17]
And opening that directly from IRB with edit "universe/galaxy/twin_sun.rb"
will show the exact line where define_method #{name}?
is written:
Finding source location with show_source
IRB command
If you want to directly get a glimpse in IRB of the source code, you can use show_source
show_source twin_sun.twin_sun?
The result will be:
From: universe/galaxy/twin_sun.rb:17
define_method "#{name}?" do
true
end
This is super cool => it is showing the place where define_method
is used to create the twin_sun?
method:
Rails
I am using for this a newly generated Rails app (using Ruby 3.2.2 and Rails 7.1) where I just added two models. They are very simple:
# db/schema.db
create_table "galaxies", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "stars", force: :cascade do |t|
t.string "name"
t.integer "galaxy_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["galaxy_id"], name: "index_stars_on_galaxy_id"
end
add_foreign_key "stars", "galaxies"
# app/models/galaxy.rb
class Galaxy < ApplicationRecord
has_many :stars
end
# app/models/star.rb
class Star < ApplicationRecord
belongs_to :galaxy
end
Let's start the Rails console with rails c
and try to find the source code or location for some methods.
Assume there is already a record for Galaxy with name Andromeda
and a couple of stars in that galaxy: Alpheratz
and Mirach
.
Finding using source_location
We might want to load the galaxy with name Andromeda
by using Galaxy.find_by
but where is that method defined?
Galaxy.method(:find_by).source_location
# =>
# [".gem/ruby/3.2.2/gems/activerecord-7.1.2/lib/active_record/core.rb", 256]
And if we open that file, we will get:
What about exists?
from Active Record? That works too indicating where exists?
is defined:
Galaxy.method(:exists?).source_location
# =>
# [".gem/ruby/3.2.2/gems/activerecord-7.1.2/lib/active_record/querying.rb", 23]
Finding the source using show_source
show_source Galaxy.find_by
The result of executing that line will be:
From: .gem/ruby/3.2.2/gems/activerecord-7.1.2/lib/active_record/core.rb:256
def find_by(*args) # :nodoc:
return super if scope_attributes?
hash = args.first
return super unless Hash === hash
hash = hash.each_with_object({}) do |(key, value), h|
# ...
I only included here the first few lines from the find_by
method but you can notice that it correctly displayed the source code.
Finding source code for params
in the Rails controller
I generated a simple scaffolding for Galaxies and I will use the debug
gem (included by default in Rails 7.1) to start an IRB session in the context of the index
action:
# app/controllers/galaxies_controller.rb
class GalaxiesController < ApplicationController
before_action :set_galaxy, only: %i[ show edit update destroy ]
# GET /galaxies or /galaxies.json
def index
binding.irb
@galaxies = Galaxy.all
end
# ... more code
Using source_location
Knowing that params is a method (can be easily checked with self.methods.include?(:params)
inside the action) I can do the following:
self.method(:params).source_location # works also without self
# =>
[".gem/ruby/3.2.2/gems/actionpack-7.1.2/lib/action_controller/metal/strong_parameters.rb", 1281]
Using show_source
show_source params
# =>
From: .gem/ruby/3.2.2/gems/actionpack-7.1.2/lib/action_controller/metal/strong_parameters.rb:1281
def params
@_params ||= begin
context = {
controller: self.class.name,
action: action_name,
request: request,
params: request.filtered_parameters
}
Parameters.new(request.parameters, context)
end
end
Exploring params
with ls
You can find out more information about params
if you use ls
command:
irb(#<GalaxiesController:0x000000...):004> ls params
ActionController::Parameters#methods:
== [] []= always_permitted_parameters always_permitted_parameters= as_json compact
compact! compact_blank compact_blank! converted_arrays deep_dup deep_merge? deep_transform_keys
deep_transform_keys! delete delete_if dig each each_key each_pair
each_value empty? encode_with eql? except exclude? extract!
extract_value fetch has_key? has_value? hash include? init_with
inspect keep_if key? keys member? merge merge!
permit permit! permitted? reject reject! require required
reverse_merge reverse_merge! select select! slice slice! stringify_keys
to_h to_hash to_param to_query to_s to_unsafe_h to_unsafe_hash
transform_keys transform_keys! transform_values transform_values! value? values values_at
with_defaults with_defaults! without
ActiveSupport::DeepMergeable#methods: deep_merge deep_merge!
instance variables: @logging_context @parameters @permitted
class variables:
@@action_on_unpermitted_parameters @@allow_deprecated_parameters_hash_equality @@always_permitted_parameters @@permit_all_parameters
When show_source
and source_location
does not work
When methods are defined with method_missing
In case you have your behavior defined with method_missing
then source_location
and show_source
will not work:
# frozen_string_literal: true
module Universe
module Galaxy
class Sun < Star
DYNAMIC_METHODS = %i(life?).freeze
def initialize
super('Sun')
end
def our_planet
'Earth'
end
private
def respond_to_missing?(name, include_private = false)
DYNAMIC_METHODS.include?(name) || super
end
def method_missing(name, *args)
return super unless DYNAMIC_METHODS.include?(name)
if name == :life?
return true
end
end
end
end
end
In this case if you load this into IRB:
require_relative "universe/galaxy/sun"
sun = Universe::Galaxy::Sun.new
You can notice that you can execute sun.life?
and even check sun.respond_to?(:life?)
:
sun.life? # => true
sun.respond_to?(:life?) # => true
You will then notice that life?
is not a method when investigating with:
sun.methods.include?(:life?) # => false
sun.private_methods.include?(:life?) # => false
Universe::Galaxy::Sun.public_methods.include?(:life?) # false
Universe::Galaxy::Sun.protected_methods.include?(:life?) # false
Universe::Galaxy::Sun.private_methods.include?(:life?) # false
Universe::Galaxy::Sun.singleton_methods.include?(:life?) # false
Universe::Galaxy::Sun.instance_methods.include?(:life?) # false
You can still get the Method
object for it:
sun.method(:life?)
# => #<Method: Universe::Galaxy::Sun#life?(*)>
But source_location
or show_source
will not work:
sun.method(:life?).source_location
# => nil
show_source sun.life?
# => Error: Couldn't locate a definition for sun.life?
It is also not displayed when using ls
on the sun
object:
ls sun
# =>
# Universe::Galaxy::Sun#methods: our_planet
# Universe::Galaxy::Star#methods: name name=
# instance variables: @name
On the Kernel, BasicObject and Object methods
In general source_location
and show_source
will show you the Ruby code that defined a method, but will not show you any C code behind any method that you are using in Ruby.
So for example the following will not work:
# respond_to? is inherited from Object
sun.method(:respond_to?)
# => #<Method: Universe::Galaxy::Sun(Kernel)#respond_to?(*)>
show_source sun.respond_to?
# => Error: Couldn't locate a definition for sun.respond_to?
sun.method(:respond_to?).source_location
# => nil
# == is inherited from BasicObject
sun.method(:==)
# => #<Method: Universe::Galaxy::Sun(BasicObject)#==(_)>
sun.method(:==).source_location
# => nil
show_source sun.==
# Error: Couldn't locate a definition for sun.==
If you want to make this work you have to use Ruby TracePoint but that is out of the scope of this article.
Conclusion
I think Ruby has now very good tools to navigate to the source of a method either from the class or from the instantiated object. Using IRB
and debug
gem makes this navigation easy, and I would recommend anyone learning Ruby to keep an irb
or rails console
session open. That way, whenever you have a question about how some particular method works, you can navigate to the source of that method and read the code.
I would like to thank Stephen Margheim and Stan Lo for reviewing an early version of this article and providing valuable feedback.
Enjoyed this article?
๐ Join my Short Ruby News newsletter for weekly Ruby updates from the community and visit rubyandrails.info, a directory with learning content about Ruby.
๐ Subscribe to my Ruby and Ruby on rails courses over email at learn.shortruby.com - effortless learning anytime, anywhere
๐ค Let's connect on Ruby.social or Linkedin or Twitter where I post mainly about Ruby and Rails.
๐ฅ Follow me on my YouTube channel for short videos about Ruby