Pergunta

Nós recentemente teve um problema onde, depois de ter ocorrido uma série de commits, um processo servidor não conseguiu executar. Agora, fomos bons rapazes pequenos e meninas e rake test correu atrás de cada check-in, mas, devido a algumas esquisitices no carregamento de biblioteca do Rails, só ocorreu quando ele correu diretamente do Mongrel no modo de produção.

Eu segui o bug para baixo e foi devido a uma nova jóia Rails substituir um método na classe String de uma forma que quebrou uma utilização estreita no tempo de execução Rails código.

De qualquer forma, conto longo, há uma maneira, em tempo de execução, para pedir o Ruby, onde um método foi definido? Algo como whereami( :foo ) que os retornos /path/to/some/file.rb line #45? Neste caso, dizendo-me que foi definido na classe String seria inútil, porque foi sobrecarregado por alguma biblioteca.

Não posso garantir a vida de origem no meu projeto, assim grepping para 'def foo' não necessariamente me dar o que eu preciso, para não mencionar se eu tiver muitos def foo de, às vezes eu não sei até que runtime qual eu esteja usando.

Foi útil?

Solução

This is really late, but here's how you can find where a method is defined:

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>

If you're on Ruby 1.9+, you can use 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]

Note that this won't work on everything, like native compiled code. The Method class has some neat functions, too, like Method#owner which returns the file where the method is defined.

EDIT: Also see the __file__ and __line__ and notes for REE in the other answer, they're handy too. -- wg

Outras dicas

You can actually go a bit further than the solution above. For Ruby 1.8 Enterprise Edition, there is the __file__ and __line__ methods on Method instances:

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

For Ruby 1.9 and beyond, there is source_location (thanks 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]

I'm coming late to this thread, and am surprised that nobody mentioned Method#owner.

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

Copying my answer from a newer similar question that adds new information to this problem.

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)

This has been backported to 1.8.7 by this gem:

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:

In action:

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]

On Macs with TextMate installed, this also pops up the editor at the specified location.

This may help but you would have to code it yourself. Pasted from the blog:

Ruby provides a method_added() callback that is invoked every time a method is added or redefined within a class. It’s part of the Module class, and every Class is a Module. There are also two related callbacks called method_removed() and method_undefined().

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

If you can crash the method, you'll get a backtrace which will tell you exactly where it is.

Unfortunately, if you can't crash it then you can't find out where it has been defined. If you attempt to monkey with the method by overwriting it or overriding it, then any crash will come from your overwritten or overridden method, and it won't be any use.

Useful ways of crashing methods:

  1. Pass nil where it forbids it - a lot of the time the method will raise an ArgumentError or the ever-present NoMethodError on a nil class.
  2. If you have inside knowledge of the method, and you know that the method in turn calls some other method, then you can overrwrite the other method, and raise inside that.

Maybe the #source_location can help to find where is the method come from.

ex:

ModelName.method(:has_one).source_location

Return

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

OR

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

Return

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

Very late answer :) But earlier answers did not help me

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

You might be able to do something like this:

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

Then ensure foo_finder is loaded first with something like

ruby -r foo_finder.rb railsapp

(I've only messed with rails, so I don't know exactly, but I imagine there's a way to start it sort of like this.)

This will show you all the re-definitions of String#foo. With a little meta-programming, you could generalize it for whatever function you want. But it does need to be loaded BEFORE the file that actually does the re-definition.

You can always get a backtrace of where you are by using caller().

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top