¿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.
10 answers
Esto es realmente tarde, pero así es como puede encontrar dónde se define un método:
# 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
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]
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
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.
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
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:
- Pase
nil
donde lo prohíbe - muchas veces el método levantará unArgumentError
o el siempre presenteNoMethodError
en una clase nil. - 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.
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]
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
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.
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()
.
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