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
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
It also possible to use `ActionDispatch::TestRequest.instance_method(:flash).source_location`
Avdi Grimm
Nicely done!
Jon Leighton
Aha, good point! I hadn't thought of that.
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
golden tip!
John Ryan
Oh, very cool. Nice trick! Thanks for posting.
Add your comment