ChatGPT解决这个技术问题 Extra ChatGPT

如何找到在运行时定义方法的位置?

我们最近遇到了一个问题,在发生一系列提交后,后端进程无法运行。现在,我们都是好孩子,每次签入后都会运行 rake test,但由于 Rails 的库加载存在一些奇怪的问题,只有当我们在生产模式下直接从 Mongrel 运行它时才会发生这种情况。

我跟踪了这个错误,这是由于一个新的 Rails gem 覆盖了 String 类中的一个方法,这种方式打破了运行时 Rails 代码中的一个狭窄用途。

无论如何,长话短说,有没有办法在运行时询问 Ruby 在哪里定义了一个方法?类似 whereami( :foo ) 的东西会返回 /path/to/some/file.rb line #45?在这种情况下,告诉我它是在 String 类中定义的将是没有帮助的,因为它被某个库重载了。

我不能保证源代码存在于我的项目中,因此对 'def foo' 的 grepping 不一定能满足我的需求,更不用说我是否有 许多 def foo,有时我没有直到运行时才知道我可能使用的是哪一个。

在 Ruby 1.8.7 中,专门添加了一种特殊方法来查找此信息(并且在 1.9.3 中仍然存在)...在我下面的答案中详细说明。

X
XtraSimplicity

这真的很晚了,但是您可以通过以下方式找到定义方法的位置:

http://gist.github.com/76951

# How to find out where a method comes from.
# Learned this from Dave Thomas while teaching Advanced Ruby Studio
# Makes the case for separating method definitions into
# modules, especially when enhancing built-in classes.
module Perpetrator
  def crime
  end
end

class Fixnum
  include Perpetrator
end

p 2.method(:crime) # The "2" here is an instance of Fixnum.
#<Method: Fixnum(Perpetrator)#crime>

如果您使用的是 Ruby 1.9+,则可以使用 source_location

require 'csv'

p CSV.new('string').method(:flock)
# => #<Method: CSV#flock>

CSV.new('string').method(:flock).source_location
# => ["/path/to/ruby/1.9.2-p290/lib/ruby/1.9.1/forwardable.rb", 180]

请注意,这不适用于所有内容,例如本机编译代码。 Method class 也有一些简洁的函数,例如 Method#owner,它返回定义方法的文件。

编辑:另请参阅另一个答案中的 __file____line__ 以及 REE 的注释,它们也很方便。 -- wg


source_location 似乎适用于 1.8.7-p334 使用 activesupport-2.3.14
找到方法后,试试Method的owner方法
2.method(:crime) 中的第二个是什么?
Fixnum 类的一个实例
重要提示:这不会从 method_missing 中提取任何动态定义的方法。因此,如果您的模块或祖先类在 method_missing 中包含 class_evaldefine_method,则此方法将不起作用。
J
James Adam

您实际上可以比上面的解决方案走得更远。对于 Ruby 1.8 企业版,在 Method 实例上有 __file____line__ 方法:

require 'rubygems'
require 'activesupport'

m = 2.days.method(:ago)
# => #<Method: Fixnum(ActiveSupport::CoreExtensions::Numeric::Time)#ago>

m.__file__
# => "/Users/james/.rvm/gems/ree-1.8.7-2010.01/gems/activesupport-2.3.8/lib/active_support/core_ext/numeric/time.rb"
m.__line__
# => 64

对于 Ruby 1.9 及更高版本,有 source_location(感谢 Jonathan!):

require 'active_support/all'
m = 2.days.method(:ago)
# => #<Method: Fixnum(Numeric)#ago>    # comes from the Numeric module

m.source_location   # show file and line
# => ["/var/lib/gems/1.9.1/gems/activesupport-3.0.6/.../numeric/time.rb", 63]

我在任何 Method 类实例(例如:method(:method).__file__)上的 __file____line__ 都得到“NoMethodError:未定义的方法”。
你有哪个版本的红宝石?
ruby 1.8.7 (2010-06-23 补丁级别 299) [x86_64-linux]
在 Ruby 1.9 上,m.__file__m.__line__ 已替换为 m.source_location
source_location 适用于 Ruby 1.9 及更高版本,包括 2.1
A
Alex D

我来晚了这个帖子,很惊讶没有人提到Method#owner

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A

我很惊讶您是第一个明确引用 Method class 的人。 1.9 中引入的另一个鲜为人知的宝藏:Method#parameters
C
Community

从较新的 similar question 复制我的答案,为这个问题添加了新信息。

Ruby 1.9 具有名为 source_location 的方法:

返回包含此方法的 Ruby 源文件名和行号,如果此方法未在 Ruby(即本机)中定义,则返回 nil

此 gem 已将其向后移植到 1.8.7:

ruby18_source_location

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

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 为给定方法返回文件,如果没有指定方法,则返回类的第一个文件:

红宝石 where_is 模块

在行动:

where_is(ActiveRecord::Base, :validates)

# => ["/Users/laas/.rvm/gems/ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

在安装了 TextMate 的 Mac 上,这也会在指定位置弹出编辑器。


M
Mohammad AbuShady

也许 #source_location 可以帮助找到该方法的来源。

前任:

ModelName.method(:has_one).source_location

返回

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/associations.rb", line_number_of_where_method_is]

或者

ModelName.new.method(:valid?).source_location

返回

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]

K
Ken

这可能会有所帮助,但您必须自己编写代码。从博客粘贴:

Ruby 提供了一个 method_added() 回调,每次在类中添加或重新定义方法时都会调用该回调。它是模块类的一部分,每个类都是一个模块。还有两个相关的回调,称为 method_removed() 和 method_undefined()。

http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby


t
the Tin Man

如果你可以让这个方法崩溃,你会得到一个回溯,它会告诉你它在哪里。

不幸的是,如果你不能让它崩溃,那么你就无法找到它的定义位置。如果您试图通过覆盖或覆盖它来使用该方法,那么任何崩溃都将来自您覆盖或覆盖的方法,并且它没有任何用处。

崩溃方法的有用方法:

在禁止它的地方传递 nil - 很多时候该方法会在 nil 类上引发 ArgumentError 或永远存在的 NoMethodError。如果您了解该方法的内部知识,并且知道该方法又会调用其他方法,那么您可以覆盖其他方法,并在其中引发。


如果您有权访问代码,则可以在您想要插入的代码中轻松插入 require 'ruby-debug'; debugger
“不幸的是,如果你不能让它崩溃,那么你就无法找出它是在哪里定义的。”不是真的。其他答案。
t
tig

很晚的答案:)但早期的答案对我没有帮助

set_trace_func proc{ |event, file, line, id, binding, classname|
  printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
}
# call your method
set_trace_func nil

你为什么要通过 nil
来自文档的@ArupRakshit:«将 proc 建立为跟踪处理程序,如果参数为 nil,则禁用跟踪。»
A
AShelly

您可能可以执行以下操作:

foo_finder.rb:

 class String
   def String.method_added(name)
     if (name==:foo)
        puts "defining #{name} in:\n\t"
        puts caller.join("\n\t")
     end
   end
 end

然后确保 foo_finder 首先加载类似

ruby -r foo_finder.rb railsapp

(我只弄乱了轨道,所以我不知道确切,但我想有一种方法可以像这样开始它。)

这将向您展示 String#foo 的所有重新定义。通过一点元编程,您可以将其概括为您想要的任何功能。但它确实需要在实际执行重新定义的文件之前加载。


t
the Tin Man

您始终可以使用 caller() 追溯您所在的位置。


这对于查找调用您的内容很有用,但在您尝试查找定义某些内容的位置时并不好。