首页 文章

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

提问于
浏览
311

我们最近遇到了一个问题,即在发生一系列提交后,后端进程无法运行 . 现在,我们是优秀的小男孩和女孩,并在每次办理登机手续后都跑了 rake test 但是,由于Rails库加载中的一些奇怪现象,它只发生在我们在 生产环境 模式下直接从Mongrel运行时 .

我追踪了这个错误,这是因为一个新的Rails gem在一个方法中覆盖了一个方法,该方法打破了运行时Rails代码中的一个狭隘用法 .

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

我不能保证源代码存在于我的项目中,因此对 'def foo' 的求助并不一定会给我我需要的东西,更不用说我是否有很多__10810_ 's, sometimes I don' t知道直到运行时我可能正在使用它 .

10 回答

  • 12

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

    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
    
  • 2

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

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

    这真的很晚了,但是你可以在这里找到定义方法的地方:

    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

  • 5

    也许 #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]
    
  • 2

    你可以做这样的事情:

    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的所有重新定义 . 通过一些元编程,您可以将其概括为您想要的任何功能 . 但它确实需要在实际进行重新定义的文件之前加载 .

  • 6

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

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

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

  • 4

    从较新的similar question复制我的答案,为此问题添加新信息 .

    Ruby 1.9 有一个名为source_location的方法:

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

    这个gem被反向移植到 1.8.7

    所以你可以请求方法:

    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(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上,这也会弹出指定位置的编辑器 .

  • 399

    实际上你可以比上面的解决方案更进一步 . 对于Ruby 1.8 Enterprise Edition, 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]
    
  • 37

    您总是可以使用 caller() 来回溯您所在的位置 .

  • 3

    如果你可以崩溃这个方法,你会得到一个回溯,它会告诉你确切的位置 .

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

    崩溃方法的有用方法:

    • 传递 nil 它禁止它 - 很多时候该方法将在一个零级别上引发 ArgumentError 或永远存在的 NoMethodError .

    • 如果你对方法有内在的了解,并且你知道该方法依次调用其他方法,那么你可以覆盖另一个方法,并在其中加注 .

相关问题