¿Cómo encontrar dónde se define un método en tiempo de ejecución?


Recientemente tuvimos un problema en el que, después de una serie de confirmaciones, un proceso de backend no pudo ejecutarse. Ahora, éramos buenos niños y niñas y corríamos rake test después de cada check-in, pero, debido a algunas rarezas en la carga de la biblioteca de Rails, solo ocurría cuando lo ejecutábamos directamente desde Mestrel en modo de producción.

Rastreé el error y se debió a que una nueva gema Rails sobrescribió un método en la clase String de una manera que rompió un uso estrecho en los Rails de tiempo de ejecución codificar.

De todos modos, resumiendo, ¿hay alguna manera, en tiempo de ejecución, de preguntar a Ruby dónde se ha definido un método? Algo como whereami( :foo ) que devuelve /path/to/some/file.rb line #45? En este caso, decirme que fue definido en la cadena de clase sería inútil, porque fue sobrecargado por alguna biblioteca.

No puedo garantizar que la fuente viva en mi proyecto, por lo que aceptar 'def foo' no necesariamente me dará lo que necesito, por no mencionar si tengo muchos def foo's, a veces no lo sé hasta el tiempo de ejecución que uno que podría estar usando.

Author: the Tin Man, 2008-10-06

10 answers

Esto es realmente tarde, pero así es como puede encontrar dónde se define un método:

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>

Si estás en Ruby 1.9+, puedes usar 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]

Tenga en cuenta que esto no funcionará en todo, como el código compilado nativo. La clase de método también tiene algunas funciones limpias, como Method#owner que devuelve el archivo donde se define el método.

EDITAR: También ver el __file__ y __line__ y notas para REE en la otra respuesta, también son útiles. -- wg

 391
Author: wesgarrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-16 03:34:36

En realidad puede ir un poco más allá de la solución anterior. Para Ruby 1.8 Enterprise Edition, existen los métodos __file__ y __line__ en las instancias Method:

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

Para Ruby 1.9 y más allá, hay source_location (gracias 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]
 80
Author: James Adam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-15 10:20:18

Estoy llegando tarde a este hilo, y me sorprende que nadie mencionó Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
 36
Author: Alex D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-20 04:07:21

Copiando mi respuesta de una nueva pregunta similar que agrega nueva información a este problema.

Ruby 1.9 tiene método llamado source_location:

Devuelve el nombre del archivo de origen de Ruby y el número de línea que contiene este método o nil si este método no se definió en Ruby (es decir, nativo)

Esto ha sido reportado a 1.8.7 por esta gema:

Así que puede solicitar el método:

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

Y luego preguntar por el source_location de ese método:

m.source_location

Esto devolverá un array con nombre de archivo y número de línea. Por ejemplo, para ActiveRecord::Base#validates esto devuelve:

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]

Para clases y módulos, Ruby no ofrece soporte incorporado, pero hay una excelente idea que se basa en source_location para devolver el archivo para un método dado o el primer archivo para una clase si no se especificó ningún método:

En acción:

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]

En Mac con TextMate instalado, esto también muestra el editor en la ubicación especificada.

 11
Author: Laas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:14

Esto puede ayudar, pero tendría que codificarlo usted mismo. Pegado desde el blog:

Ruby proporciona un method_added() callback que se invoca cada vez que un método se añade o se redefine dentro de un clase. Es parte de la clase Module, y cada Clase es un Módulo. Hay también dos callbacks relacionados llamados method_removed() y method_undefined().

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

 6
Author: Ken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 20:01:29

Si puede bloquear el método, obtendrá una traza inversa que le dirá exactamente dónde está.

Desafortunadamente, si no puede bloquearlo, entonces no puede averiguar dónde se ha definido. Si intenta mono con el método por sobrescribirlo o primordial, entonces cualquier accidente del sobrescrito o método anulado, y no habrá ningún uso.

Formas útiles de métodos de choque:

  1. Pase nil donde lo prohíbe - muchas veces el método levantará un ArgumentError o el siempre presente NoMethodError en una clase nil.
  2. Si tienes conocimiento interno del método, y sabes que el método a su vez llama a algún otro método, entonces puedes sobrescribir el otro método, y subir dentro de eso.
 5
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-22 15:22:03

Tal vez el #source_location puede ayudar a encontrar de dónde viene el método.

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]

O

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]
 4
Author: Samda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-27 15:11:15

Respuesta muy tardía:) Pero las respuestas anteriores no me ayudaron

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
 3
Author: tig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-14 18:36:18

Usted podría ser capaz de hacer algo como esto:

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

Luego asegúrese de que foo_finder se cargue primero con algo como

ruby -r foo_finder.rb railsapp

(Solo me he metido con rails, así que no lo sé exactamente, pero imagino que hay una manera de empezar de esta manera.)

Esto le mostrará todas las re-definiciones de String#foo. Con un poco de meta-programación, se podría generalizar para cualquier función que desee. Pero necesita ser cargado ANTES del archivo que en realidad hace la re-definición.

 2
Author: AShelly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 20:16:47

Siempre puede obtener una traza inversa de dónde se encuentra utilizando caller().

 2
Author: the Tin Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-31 23:34:37