ruby on rails - software - ¿Cómo encontrar dónde se define un método en tiempo de ejecución?
ruby on rails tutorial (10)
Recientemente tuvimos un problema donde, después de una serie de confirmaciones, no se pudo ejecutar un proceso de back-end. Ahora, éramos buenos niños y niñas y rake test
después de cada registro pero, debido a algunas rarezas en la carga de la biblioteca de Rails, solo ocurrió cuando lo ejecutamos directamente desde Mongrel en modo de producción.
Rastreé el error y se debió a que una nueva gema de Rails sobrescribía un método en la clase String de una manera que rompió un uso limitado en el código de Rails en tiempo de ejecución.
De todos modos, cuento, ¿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 se definió en la clase Cadena no sería útil, ya que estaba sobrecargado por alguna biblioteca.
No puedo garantizar que la fuente viva en mi proyecto, por lo que grepping para ''def foo''
no necesariamente me dará lo que necesito, por no mencionar si tengo muchos def foo
''s, a veces no sé hasta el tiempo de ejecución cuál soy puede estar usando.
Copiando mi respuesta de una pregunta similar más reciente que agrega nueva información a este problema.
Ruby 1.9 tiene un método llamado source_location :
Devuelve el nombre de 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 backported a 1.8.7 por esta gema:
Así que puedes solicitar el método:
m = Foo::Bar.method(:create)
Y luego pregunte por la source_location
de origen de ese método:
m.source_location
Esto devolverá una matriz 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 las clases y los módulos, Ruby no ofrece soporte integrado, pero hay un excelente Gist por ahí 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 abre el editor en la ubicación especificada.
En realidad, puede ir un poco más lejos que la solución anterior. Para Ruby 1.8 Enterprise Edition, existen los métodos __file__
y __line__
en las instancias de 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]
Es posible que puedas 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úrate de que foo_finder se carga primero con algo como
ruby -r foo_finder.rb railsapp
(Solo me he metido con los rieles, así que no sé exactamente, pero me imagino que hay una manera de comenzar de esta manera).
Esto te mostrará todas las re-definiciones de String # foo. Con un poco de meta-programación, podría generalizarlo para cualquier función que desee. Pero debe cargarse ANTES del archivo que realmente hace la redefinición.
Esto es realmente tarde, pero aquí es cómo 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 Método también tiene algunas funciones claras, como Method#owner que devuelve el archivo donde se define el método.
EDITAR: También vea el __file__
y __line__
y las notas para REE en la otra respuesta, también son útiles. - wg
Esto puede ayudar, pero tendría que codificarlo usted mismo. Pegado desde el blog:
Ruby proporciona una devolución de llamada method_added () que se invoca cada vez que se agrega o se redefine un método dentro de una clase. Es parte de la clase Módulo, y cada Clase es un Módulo. También hay dos devoluciones de llamada relacionadas denominadas method_removed () y method_undefined ().
http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby
Llego tarde a este hilo y me sorprende que nadie haya mencionado al Method#owner
.
class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
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
Si puedes bloquear el método, obtendrás un seguimiento que te dirá exactamente dónde está.
Desafortunadamente, si no puede bloquearlo, entonces no puede descubrir dónde se ha definido. Si intenta simularse con el método sobrescribiéndolo o anulándolo, entonces cualquier falla provendrá de su método sobrescrito o anulado, y no será de ninguna utilidad.
Formas útiles de chocar métodos:
- Pase
nil
donde lo prohíba: laNoMethodError
de las veces, el método generará unArgumentError
o el siempre presenteNoMethodError
en una clase nula. - Si tiene conocimiento interno del método, y sabe que el método a su vez llama a otro método, entonces puede sobrescribir el otro método y subir dentro de él.
Siempre puede obtener un seguimiento de dónde se encuentra utilizando caller()
.
Tal vez la #source_location
pueda ayudar a encontrar de dónde proviene el método.
ex:
ModelName.method(:has_one).source_location
Regreso
[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
Regreso
[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]