从类对象获取类位置

59

我有一堆代码要查看,现在是调试时间。由于我从来不喜欢 Ruby 的调试器,所以我正在寻找一种浏览代码并阅读它的方法。

我尝试做的是获取加载类定义的文件位置:

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

对于更小、组织得更好的项目,我只会搜索class Bar,但在这里不可能,因为有很多名为Bar的类,并且更糟糕的是,其中一些类在同一个命名空间下。我知道,这是一场麻烦的等待。

注意:我正在使用 Ruby 1.8.7。


仅供参考,这与https://dev59.com/tnVC5IYBdhLWcg3w0EoD非常相似,但它们的解决方案都不适用于我。`__file__`和`__line__`对我无效。 - Haris Krajina
1
你说它不工作是什么意思?怎么不工作?(顺便说一下,应该是__FILE____LINE__ - tomferon
source_location 方法计划在 此错误报告 中被倒退到 1.8.8。 - awendt
2
这在代码检查时可能会很麻烦,但使用调试器就很容易了。只需在调用点之前设置断点,然后步入方法,调试器列表会告诉您到达的位置。仅仅因为你不是粉丝并不意味着你不应该使用可用的工具。 - dbenhur
@thoferon 我提供的链接中提到了在Method对象下调用__file__以获取源文件,但它不起作用。当然,__FILE__是可以工作的 :) - Haris Krajina
8个回答

52

对于 Ruby 1.9 的 MethodsProcs,有一个名为 source_location 的方法:

返回包含该方法的 Ruby 源文件名和行号,如果该方法没有在 Ruby 中定义(即本地方法),则返回 nil。

因此,您可以请求该方法:

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

然后要求该方法的 source_location

m.source_location

这将返回一个具有文件名和行号的数组。 例如对于ActiveRecord::Base#validates,它返回:

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]

Ruby对于类和模块并没有提供内置的支持,但是有一个出色的Gist构建在source_location上,用于返回给定方法的文件或者如果未指定方法,则是第一个类的文件:

编辑:对于Ruby 1.8.7,有一个将source_location回溯的gem可用:


是的,我注意到有点晚了。 :-( - Laas
刚试了一下ruby18_source_location,它很好用。我同意其他人的看法,调试是一个不错的方法,但这种方式让我工作更快。此外,我建议在以下更受欢迎的链接上分享这个方法:https://dev59.com/tnVC5IYBdhLWcg3w0EoD。谢谢。 - Haris Krajina
2
了解你所编写语言中的调试器是非常重要的技能。我会教给我一起工作的人,因为它可以让你深入了解代码正在做什么,而且当我们遇到问题需要知道在哪里和为什么时,这是我首先使用的工具。 - the Tin Man
据我所知,Foo::Bar.method(:create)将带您进入类方法Foo::Bar.create,而不是实例方法Foo::Bar#create。同样的道理适用于ActiveRecord::Base.method(:validates),它代表ActiveRecord::Base.validates,而不是ActiveRecord::Base#validates。要查找实例方法的位置,您可以使用Foo::Bar.instance_method(:create).source_location - x-yuri
Ruby 2.7提供了对Module.const_source_location(:ClassName)的支持。参考 - https://blog.saeloun.com/2019/09/17/ruby-2-7-module-const-source-location.html - Valarpirai

40

提醒一下,在Rails的控制台或Rails应用程序的调试会话中,您可以找到定义特定类的文件的磁盘位置。例如:

> show-source Job
这将为您提供。
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

你需要先加载类,例如在控制台中调用 Job。然后 show-source 就可以工作了。大多数情况下。 - ciastek
1
这需要在你的项目中包含并加载 pry gem。 - CTS_AE

12

查找一个具有.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]

要查找一个模块或类的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]

1
const_source_location 在 Ruby 2.7 及更高版本中可用。) - Sam
2
这应该是2022年的最佳答案! - jupp0r

3
以下是一个简单的示例,展示了我如何在代码中跟踪位置。如果我需要知道模块中的位置:
class Foo
  attr_reader :initialize_loc
  def initialize
    @initialize_loc = [__FILE__, __LINE__]
    # do more stuff...
  end
end

如果我需要知道某事发生的地点:

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

当我运行代码时,我得到以下结果:
FooBar:Desktop foobar ruby t2.rb 
foo initilized at /Users/foobar/Desktop/t1.rb:4

如果我不想动模块的源代码,而且希望在需要时调试器可以跳入其中,我只需让调试器做如下操作:

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

执行将停止,我会跳到 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) 

一个简单的s将会“步进”到下一行代码,该行代码将在Fooinitialize块中:

(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) 

除此之外,使用像 grep -rn target_to_find path_to_search 这样的工具来递归搜索目录并列出与目标匹配的行的文件名和行号,将大大帮助您找到您想要的内容。
或者,在 Vim 中使用 :vim /target_to_find/ path_to_search 将返回您要查找的文件。

谢谢你,实际上是非常聪明的解决方案 :) 我会设置一个类来用于此目的。 - Haris Krajina
我不是100%确定。但是当你使用@initialize_loc = [__FILE__, __LINE__]定义(或重新定义)初始化方法时,这意味着以后您将获得该行和文件的“坐标”,无论您将其放在何处,这对于很多东西都很好,但不适用于@Dolphin所问的问题。他想找到一个他从一开始就不知道来自哪里的声明。 - Regedor
“独立于放置位置”?在文件中,哪些内容是与实际物理位置无关的? - the Tin Man

0
说实话,根据你所描述的代码组织方式,我认为使用ruby-debug是发现调用位置目标的最简单方法:只需设置断点并逐步执行即可。如果你真的对调试器过敏,可以在调用位置使用Kernel#set_trace_func来检测。

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
}

0

当我改变一个对象的超类时,出现了这个错误,解决方法是停止和启动Spring。


0

坏消息!我猜在运行时无法知道在Ruby 1.8.7中创建或定义类的文件。

如果项目有一些结构,比如Rails,你就能猜到它。

但是在Ruby中,多个文件可以为同一个类定义方法,甚至可以在运行时定义类(元编程)。

这意味着可能有多个地方定义了该类。而你要查找的内容可能分布在多个文件中。

我猜你将不得不搜索Bar的所有定义,并查看它们是否在Foo模块内,或者首先查找所有Foo定义并检查其内部内容。 如果代码很混乱,我看不到简单的方法,你将不得不从头到尾跟踪代码。 一个好的编辑器和多文件搜索可能会有所帮助,但你需要仔细阅读代码。

编辑:好消息是,在 Ruby 1.9 中有 source_location,看起来也有向后移植到1.8.7的工具。然而,如果定义是在运行时通过 eval 或类似方式进行的,则不确定它是否有效。我认为最简单的解决方案是使用像 Rubymine 这样的好编辑器,通常可以告诉您代码定义的位置。


我猜在运行时无法知道是哪个文件创建或定义了一个类。那么__FILE____LINE__有什么作用呢? - the Tin Man
@theTinMan __LINE__ 告诉你在代码中的值,OP 知道调用站点,而不是定义站点,那么您建议他将 __LINE__ 放在哪里以揭示他正在调用的方法所在的位置? - dbenhur
请查看我对这个问题的回答。 - the Tin Man

-1
Klass.method(Klass.methods.first).source_location

使用source_location来查找类中定义的第一个方法。我想这并不是百分之百可靠的,因为可能会受到元编程和其他技巧的影响。

方法可以被继承或混入,但这种方式不可靠。 - Anthony
这有点靠运气,特别是当你试图找到Rails类时。 - Paul Danelli
2
这让我朝着正确的方向前进:Klass.instance_method(Klass.instance_methods(false).first).source_location - Gregor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接