Tracking down method definitions in Ruby

One of my favourite features of Ruby 1.9 is the #source_location method of Proc and Method. Let me explain. Often I am confronted with a large code base (usually Rails), and want to figure out exactly where some method on some object is defined. Rails has many dark corners, and sometimes finding things isn’t entirely straightforward.

A trick that I often use is to get a Method object for the method that I want to find, and then to print out its location. Like this:

class Foo
  def bar
    "omg, where am I?"
  end
end

p Foo.new.method(:bar).source_location # found you!

Recently I was trying to find out where the #flash method of ActionDispatch::TestRequest was defined. It’s not defined in the main files that contain the definitions for ActionDispatch::TestRequest or ActionDispatch::Request (which is the superclass).

So I pulled out my standard tool:

r = ActionDispatch::TestRequest.new
p r.method(:flash).source_location

It didn’t work:

ArgumentError: wrong number of arguments (1 for 0)

It turns out that ActionDispatch::Request has its own method method, which relates to the HTTP method. Hence Ruby’s method method is overridden.

However, all was not lost! Ruby also makes it possible to get a reference to a method definition that isn’t bound to any particular object. This is called an UnboundMethod, and you can’t call it until you bind it to some object. So I was able to get an unbound reference to the original method method and then bind it to my object. Like so:

r = ActionDispatch::TestRequest.new
meth = Object.instance_method(:method)
p meth.bind(r).call(:flash).source_location # found it!

It turns out that the Request class is reopened in actionpack/lib/action_dispatch/middleware/flash.rb and the flash method gets defined there.

Comments

I'd love to hear from you here instead of on corporate social media platforms! You can also contact me privately.

ryanlecompte's avatar

ryanlecompte

Also check out my method_locator gem to get a bit more insight into Ruby's method lookup path! https://github.com/ryanleco...

Sergey Nartimov's avatar

Sergey Nartimov

It also possible to use `ActionDispatch::TestRequest.instance_method(:flash).source_location`

Avdi Grimm's avatar

Avdi Grimm

Nicely done!

Jon Leighton's avatar

Jon Leighton

Aha, good point! I hadn't thought of that.

Paweł Gościcki's avatar

Paweł Gościcki

[DEV] main:0> Class::respond_to?(:public, true)
=> true

[DEV] main:0> Class::method(:public)
=> #<method: class(module)#public="">

[DEV] main:0> Class::method(:public).source_location
=> nil

Bummer :(

Antonio Lorusso's avatar

Antonio Lorusso

golden tip!

John Ryan's avatar

John Ryan

Oh, very cool. Nice trick! Thanks for posting.

Add your comment