Skip to main content

Command Palette

Search for a command to run...

Finding where a method is defined in Ruby using IRB

Tracing method definitions in Ruby via IRB: Clear and concise examples

Updated
10 min read
Finding where a method is defined in Ruby using IRB
L

Senior Product Engineer, working in Ruby and Rails. Passionate about idea generation, creativity, and programming.

I curate the Short Ruby Newsletter.

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 object

  • using the IRB show_source command

  • displaying 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

Learning Ruby and Rails

Part 2 of 7

My recommendations about how to approach the learning of Ruby and Rails. I will focus on Ruby and on Rails but from time to time will also write about other web frameworks.

Up next

How to create a new Rails app running Rails 7.1 beta or main branch

Create a new app with latest Rails

More from this blog

All about code - Ruby and Rails technical content written by Lucian Ghinda

102 posts

I write here quick thoughts, ideas, tips, and learnings about programming, programmers, and building software. Most of my focus is on Ruby, Rails, Hotwire, and everything about web applications.