Pregunta

Recientemente tuvimos un problema en el que, 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 corrimos 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 forma, 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 , a veces no sé hasta el tiempo de ejecución cuál puedo estar usando.

¿Fue útil?

Solución

Esto es muy tarde, pero aquí es cómo 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 interesantes, como Method # owner que devuelve el archivo donde se encuentra El método está definido.

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

Otros consejos

Puedes 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]

Llego tarde a este hilo y me sorprende que nadie mencione Method # owner .

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

Copiando mi respuesta de una nueva pregunta similar 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)

Esta gema ha devuelto este paso hacia 1.8.7 :

Para que pueda solicitar el método:

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

Y luego pregunte por source_location de ese método:

m.source_location

Esto devolverá una matriz con 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/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 hay. se especificó el 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 Macs con TextMate instalado, esto también abre el editor en la ubicación especificada.

Esto puede ayudar pero deberías codificarlo tú mismo. Pegado desde el blog:

  

Ruby proporciona un method_added ()   devolución de llamada que se invoca cada vez que un   El método se agrega o se redefine dentro de una   clase. Es parte de la clase Módulo,   y cada clase es un modulo. Existen   también dos llamadas relacionadas devoluciones de llamada   method_removed () y   method_undefined ().

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

Si puedes bloquear el método, obtendrás un seguimiento que te dirá exactamente dónde está.

Desafortunadamente, si no puedes bloquearlo, entonces no puedes averiguar dónde se ha definido. Si intentas simularlo con el método sobrescribiéndolo o anulandolo, entonces cualquier falla provendrá de tu método sobrescrito o anulado, y no tendrá ningún uso.

Formas útiles de romper métodos:

  1. Pase nil donde lo prohíba: la mayoría de las veces, el método generará un ArgumentError o el siempre presente NoMethodError en un clase nula.
  2. 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.

Quizás #source_location puede ayudar a encontrar de dónde proviene el método.

ej:

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]

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

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 cargue 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 iniciarlo de esta manera).

Esto te mostrará todas las redefiniciones 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.

Siempre puedes obtener un seguimiento de dónde estás usando caller () .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top