2008-10-06 11 views
290

Recientemente tuvimos un problema en el que, después de una serie de confirmaciones, no se ejecutó un proceso de fondo. 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 ocurrió cuando lo ejecutamos directamente desde Mongrel en modo de producción.¿Cómo encontrar dónde se define un método en tiempo de ejecución?

Seguí el error y se debió a una nueva gema de Rails sobrescribiendo un método en la clase String de una manera que rompió un uso restringido en el código Rails de tiempo de ejecución.

De todos modos, el cuento largo, hay una manera, en tiempo de ejecución, para pedir Rubí, donde 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 clase String sería inútil, ya que estaba sobrecargado por alguna biblioteca.

no puedo garantizar la vida de código en mi proyecto, por lo grepping para 'def foo' no necesariamente va a darme lo que necesito, por no hablar de si tengo muchos def foo 's, a veces no sé hasta que el tiempo de ejecución, que uno que pueda estar usando.

+1

en Ruby 1.8.7, un especial El método fue agregado específicamente para encontrar esta información (y todavía está allí en 1.9.3) ... detalles en mi respuesta a continuación. –

Respuesta

373

Esto es muy tarde, pero aquí es cómo se puede encontrar donde 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) 
#<Method: Fixnum(Perpetrator)#crime> 

Si se encuentra en Rubí 1.9+, puede utilizar 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 nativo compilado. El Method class también tiene algunas funciones ordenadas, como Method#owner, que devuelve el archivo donde está definido el método.

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

+1

ubicación_origen parece funcionar para 1.8.7-p334 con activesupport-2.3.14 – MaasSql

+0

después de encontrar el método, pruebe el método 'owner' de Method – Juguang

+0

¿Cuál es el número dos en' 2.method (: crime) '? – stack1

6

Esto puede ayudar, pero que tendría que codificar por sí mismo. Pegado desde el 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

4

Si puede chocar el método, podrás conseguir una traza que le dirá exactamente donde está.

Por desgracia, si no puede chocar que entonces no se puede averiguar dónde se ha definido. Si intenta simular el método al sobreescribirlo o anularlo, cualquier bloqueo vendrá de su método sobrescrito o reemplazado, y no será de ninguna utilidad.

formas útiles de métodos rompiendo:

  1. nil Pass donde lo prohíbe - una gran parte del tiempo el método lanzará una ArgumentError o la siempre presente NoMethodError en una clase nula.
  2. Si usted tiene el conocimiento interno del método, y usted sabe que el método a su vez requiere algún otro método, entonces usted puede overrwrite el otro método, y elevar el interior de eso.
+0

Si tiene acceso al código, puede simplemente insertar 'require 'ruby-debug'; depurador' en su código donde desea ingresar. –

2

Usted puede 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 

a continuación, asegurar foo_finder se carga primero con algo así como

ruby -r foo_finder.rb railsapp 

(I' Sólo me he metido con los rieles, así que no sé exactamente, pero me imagino que hay una manera de comenzar algo como esto.)

Esto le mostrará todas las redefiniciones de String # foo. Con un poco de meta-programación, podrías generalizarlo para cualquier función que desees. Pero necesita ser cargado ANTES del archivo que realmente hace la redefinición.

2

Siempre se puede conseguir una traza de dónde se encuentra mediante el uso de caller().

+0

Esto es útil para encontrar lo que le llamó, pero no es bueno cuando está tratando de encontrar dónde se definió algo. –

3

respuesta muy tarde :) Pero las respuestas anteriores no me ayudan a

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 
+0

¿Por qué estás pasando 'nil'? –

+0

@ArupRakshit de la documentación: «Establece proc como el controlador para el seguimiento, o deshabilita el seguimiento si el parámetro es' nil '. » – tig

78

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

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] 
+0

Me gusta ese. – mikezter

+1

Obtengo "NoMethodError: método indefinido" para '' __file__' y '__line__' en cualquier instancia de clase' Method', ej: 'method (: method) .__ file__'. –

+0

¿Qué versión de rubí tienes? –

34

vengo tarde en este hilo, y estoy sorprendido de que nadie haya mencionado Method#owner.

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

Me sorprende que seas el primero en hacer referencia a la [Clase de método] (http://www.ruby-doc.org/core-1.9.3/ Method.html) explícitamente. Otro tesoro menos conocido introducido en 1.9: 'Parámetros de método #. – fny

10

Copia de mi respuesta de una nueva similar question que añade nueva información a este problema.

Rubí 1,9 ha método llamado 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)

Esto ha sido portado a 1.8.7 por esta joya:

Por lo que puede solicitud del método:

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

y luego pedir la source_location de ese método:

m.source_location 

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

ActiveRecord::Base.method(:validates).source_location 
# => ["/Users/laas/.rvm/gems/[email protected]/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81] 

Para las clases y módulos, Ruby no ofrece un soporte incorporado, pero hay una excelente Gist por ahí que se basa en source_location a presentar declaración de un método determinado o primer archivo para una clase si se ha especificado ningún método:

en acción:

where_is(ActiveRecord::Base, :validates) 

# => ["/Users/laas/.rvm/gems/[email protected]/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.

3

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

ejemplo:

ModelName.method(:has_one).source_location 

Volver

[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 

Volver

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