59

I have a bunch of code to look at, and now it is debugging time. Since I have never been a fan of Ruby's debugger I am looking for a way of going through code and reading it.

What I am trying to do is get the location of the file where a loaded class is defined:

Foo::Bar.create(:param) # how can I know file location in runtime?

For smaller, better organized, projects, I would just search for class Bar but here that is not possible since there are many classes named Bar, and, to make matters worse, some of them are under the same namespace. I know, it's trouble waiting to happen.

Note: I'm using Ruby 1.8.7.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Haris Krajina
  • 14,824
  • 12
  • 64
  • 81
  • Just as fyi this is very similar to http://stackoverflow.com/questions/175655/how-to-find-where-a-method-is-defined-at-runtime but none of their solution works for me. `__file__` and `__line__` do not work for me. – Haris Krajina Oct 22 '12 at 13:09
  • 1
    What do you mean, it doesn't work ? How ? (and btw, it's `__FILE__` and `__LINE__`) – tomferon Oct 22 '12 at 13:29
  • Method `source_location` is scheduled to be backported to 1.8.8 in [this bug report](http://bugs.ruby-lang.org/issues/2180) – awendt Oct 22 '12 at 13:33
  • 2
    This is a pain by code inspection but easy with the debugger. Just set a breakpoint before the call site, then Step into the method and the debugger listing tells you where you arrived. Just because you aren't a fan doesn't mean you shouldn't use the available tool. – dbenhur Oct 22 '12 at 13:49
  • @thoferon Link I have given mentioned invoking `__file__` under `Method` object to get source file and it does not work. Ofc `__FILE__` works :) – Haris Krajina Oct 22 '12 at 14:26

8 Answers8

52

For Methods and Procs Ruby 1.9 has method called source_location:

Returns the Ruby source filename and line number containing this method or nil if this method was not defined in Ruby (i.e. native)

So you can request for the method:

m = Foo::Bar.method(:create)

And then ask for the source_location of that method:

m.source_location

This will return an array with filename and line number. E.g for ActiveRecord::Base#validates this returns:

ActiveRecord::Base.method(:validates).source_location
# => ["/Users/laas/.rvm/gems/ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

For classes and modules, Ruby does not offer built in support, but there is an excellent Gist out there that builds upon source_location to return file for a given method or first file for a class if no method was specified:

EDIT: For Ruby 1.8.7 there is a gem that backports source_location:

Laas
  • 5,978
  • 33
  • 52
  • Yeah, noted it a bit too late. :-( – Laas Oct 22 '12 at 14:02
  • Just tried ruby18_source_location and it works. Great stuff I do agree with others that debug is good way to go but this way I work much faster. Also I would recommend sharing this on following more popular link http://stackoverflow.com/questions/175655/how-to-find-where-a-method-is-defined-at-runtime. Thank you. – Haris Krajina Oct 22 '12 at 14:27
  • 2
    Knowing the debugger in the language you're writing in is a very important skill. I teach it to the people I work with because it gives you intimate knowledge of what your code is doing, and its the first tool I grab when we have a problem and need to know where and why. – the Tin Man Oct 22 '12 at 15:13
  • AFAICT, `Foo::Bar.method(:create)` will get you to a class method `Foo::Bar.create`, not to an instance method `Foo::Bar#create`. The same goes for `ActiveRecord::Base.method(:validates)`, which represents `ActiveRecord::Base.validates`, not `ActiveRecord::Base#validates`. To find out instance method's location, you'd use `Foo::Bar.instance_method(:create).source_location`. – x-yuri Feb 01 '15 at 12:49
  • Ruby 2.7 has this support Module.const_source_location(:ClassName) Ref - https://blog.saeloun.com/2019/09/17/ruby-2-7-module-const-source-location.html – Valarpirai Feb 25 '22 at 08:44
40

FYI, In Rails's console or debugging sessions of Rails apps, you can find out the disk-location of the file where that particular class is defined. like

> show-source Job

this will give you

From: /home/john/projects/iisifix/app/models/job.rb @ line 13:
Class name: Job
Number of monkeypatches: 6. Use the `-a` option to display all available monkeypatches
Number of lines: 66

class Job < ApplicationRecord
  belongs_to :quote_request
  belongs_to :garage
Shiva
  • 11,485
  • 2
  • 67
  • 84
11

To find a function with .source_location:

> ActiveModel.method(:as_json).source_location
["/usr/local/bundle/gems/activesupport-6.1.4.4/lib/active_support/core_ext/object/json.rb", 54]

To find a module or a class Object.const_source_location:

Object.const_source_location('ActiveModel')
["/usr/local/bundle/gems/activemodel-6.1.4.4/lib/active_model/gem_version.rb", 3]
3

Here's a simple example showing how I track locations in code. If I need to know a location in a module:

class Foo
  attr_reader :initialize_loc
  def initialize
    @initialize_loc = [__FILE__, __LINE__]
    # do more stuff...
  end
end

If I need to know where something happened:

require_relative 't1'

foo = Foo.new
# do lots of stuff until you want to know where something was initialized.
puts 'foo initialized at %s:%s' % foo.initialize_loc

When I run the code I get:

FooBar:Desktop foobar ruby t2.rb 
foo initilized at /Users/foobar/Desktop/t1.rb:4

If I don't want to mess with the source-code of the module, and want the debugger to jump in when I need it, I'll have the debugger do just that:

require_relative 't1'
require 'ruby-debug'

debugger
foo = Foo.new
# do lots of stuff until you want to know where something was initilized.
puts 'foo initilized at %s:%s' % foo.initialize_loc

The execution will stop and I'll drop into the debugger at the line immediately following debugger:

[0, 9] in t2.rb
  1  require_relative 't1'
  2  require 'ruby-debug'
  3  
  4  debugger
=> 5  foo = Foo.new
  6  # do lots of stuff until you want to know where something was initilized.
  7  puts 'foo initilized at %s:%s' % foo.initialize_loc
  8  
t2.rb:5
foo = Foo.new
(rdb:1) 

A simple s will "step" me into the next line of code, which will be in the initialize block for Foo:

(rdb:1) s
[-1, 8] in /Users/foobar/Desktop/t1.rb
  1  class Foo
  2    attr_reader :initialize_loc
  3    def initialize
=> 4      @initialize_loc = [__FILE__, __LINE__]
  5      # do more stuff...
  6    end
  7  end
  8  
/Users/foobar/Desktop/t1.rb:4
@initialize_loc = [__FILE__, __LINE__]
(rdb:1) 

Beyond this, using tools like grep -rn target_to_find path_to_search to recursively search directories and list the filename and line numbers of lines matching the target, will go a long ways to helping find what you're looking for.

Or, using :vim /target_to_find/ path_to_search from inside Vim will return the files you're looking for.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Thanks you, actually very smart solution :) I will setup one class to use for this purpose – Haris Krajina Oct 22 '12 at 14:32
  • I'm not 100% sure. But when you define (or redefine) the initializer method with `@initialize_loc = [__FILE__, __LINE__]`.Means that latter you will later get the "cordinates" for that line and file, independently of where you put it, wich is a good a lot of stuff but not for what @Dolphin is asking. He wants to find a declaration that he does not know where it is from the beginning. – Regedor Oct 22 '12 at 14:39
  • "independently of where you put it"? What would be independent of the physical location in a file? – the Tin Man Oct 22 '12 at 15:26
0

Bad news! I guess in run time there is no way to know what file create or defined a class in Ruby 1.8.7.

If the project has some structure like rails, you would be able to guess it.

But in Ruby multiple files can be defining methods for the same class Class can even be defined during run time (metaprogramming).

That means that there might be more than one place where the class is defined. And what you look for can be spread over more than one file.

I guess you will have to search for all definitions of Bar and see if they are inside the module Foo, or start by find all Foo definitions and check whats inside. If the code is a mess, I don't see a easy way, you will have to follow the spaguetti form point to poi. A good editor and multiple file search might help, but you will need to read through the code.

EDIT: Some good news after all. In Ruby 1.9 there is source_location and looks like there is backport of it for 1.8.7. However, if the definition was made during runtime by a eval or so I'm not sure if it will work. I think the simplest solution is a good editor like Rubymine that usually can tell you where the code was defined.

Regedor
  • 144
  • 5
  • "I guess in run time there is no way to know what file create or defined a class."? What do `__FILE__` and `__LINE__` do then? – the Tin Man Oct 22 '12 at 13:46
  • @theTinMan `__LINE__` tells you the value where you are in the code, the OP knows the call site, not the definition site, so where do you propose he put `__LINE__` to reveal where the method he's calling is? – dbenhur Oct 22 '12 at 13:53
  • See my answer to the question. – the Tin Man Oct 22 '12 at 14:24
0

Frankly, given your described code organization, I think ruby-debug is the easy route to discovering the destination of your call-site: just set breakpoint and step in. If you're really allergic to the debugger, you could instrument the call site with Kernel#set_trace_func.

$max_trace = 10
set_trace_func proc { |event, file, line, id, binding, classname|
  printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
  $max_trace -= 1 
  set_trace_func(nil) unless $max_trace > 0
}
dbenhur
  • 20,008
  • 4
  • 48
  • 45
0

I got this error when changing a superclass of an object and the fix was to stop and start spring.

bigtoe416
  • 53
  • 1
  • 5
-2
Klass.method(Klass.methods.first).source_location

Using source_location for a method, I can search for first method defined in a class. I imagine this is not foolproof due to meta-programming and other hacks.

shushugah
  • 180
  • 1
  • 9