Bagaimana menemukan di mana metode didefinisikan saat runtime?

328

Kami baru-baru ini memiliki masalah di mana, setelah serangkaian komitmen terjadi, proses backend gagal dijalankan. Sekarang, kami adalah anak laki-laki dan perempuan yang baik dan berlari rake testsetelah setiap check-in tetapi, karena beberapa keanehan dalam pemuatan perpustakaan Rails, itu hanya terjadi ketika kami menjalankannya langsung dari Mongrel dalam mode produksi.

Saya melacak bug dan itu karena permata Rails baru menimpa metode dalam kelas String dengan cara yang memecah satu penggunaan sempit dalam kode Rails runtime.

Ngomong-ngomong, cerita panjangnya, adakah cara, saat runtime, untuk bertanya kepada Ruby di mana suatu metode telah didefinisikan? Sesuatu seperti whereami( :foo )itu kembali /path/to/some/file.rb line #45? Dalam hal ini, memberi tahu saya bahwa itu didefinisikan dalam kelas String tidak akan membantu, karena itu kelebihan beban oleh beberapa perpustakaan.

Saya tidak dapat menjamin sumber hidup dalam proyek saya, jadi mencari 'def foo'tidak akan selalu memberi saya apa yang saya butuhkan, belum lagi jika saya punya banyak def foo , kadang-kadang saya tidak tahu sampai runtime mana yang mungkin saya gunakan.

Matt Rogish
sumber
1
Di Ruby 1.8.7, metode khusus ditambahkan secara khusus untuk menemukan informasi ini (dan masih ada di 1.9.3) ... detail dalam jawaban saya di bawah.
Alex D

Jawaban:

419

Ini benar-benar terlambat, tetapi inilah cara Anda dapat menemukan tempat metode didefinisikan:

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>

Jika Anda menggunakan Ruby 1.9+, Anda dapat menggunakannya 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]

Perhatikan bahwa ini tidak akan berfungsi pada semuanya, seperti kode kompilasi asli. Kelas Metode juga memiliki beberapa fungsi yang rapi, seperti pemilik Metode # yang mengembalikan file tempat metode tersebut didefinisikan.

EDIT: Juga lihat __file__dan __line__dan catatan untuk REE di jawaban lain, mereka juga berguna. - wg

wesgarrison
sumber
1
source_location tampaknya berfungsi untuk 1.8.7-p334 menggunakan activesupport-2.3.14
Jeff Maass
setelah menemukan metode, coba metode ownerMetode
Juguang
1
Apa nomor dua 2.method(:crime)?
stack1
1
turunan dari kelasFixnum
kitiahh
1
Catatan penting: Ini tidak akan menarik metode yang ditentukan secara dinamis dari method_missing. Jadi jika Anda memiliki modul atau kelas leluhur dengan class_evalatau define_methoddi dalam method_missing, maka metode ini tidak akan berfungsi.
cdpalmer
83

Anda sebenarnya bisa melangkah lebih jauh dari solusi di atas. Untuk Ruby 1.8 Enterprise Edition, ada __file__dan __line__metode pada Methodinstance:

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

Untuk Ruby 1.9 dan selanjutnya, ada source_location(terima kasih 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]
James Adam
sumber
2
Saya mendapatkan "NoMethodError: undefined method" untuk kedua __file__dan __line__pada setiap Methodkelas misalnya, ex: method(:method).__file__.
Vikrant Chaudhary
Versi ruby ​​apa yang Anda miliki?
James Adam
ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Vikrant Chaudhary
19
Di Ruby 1.9, m.__file__dan m.__line__telah diganti dengan m.source_location.
Jonathan Tran
Bagaimana dengan Ruby 2.1?
Lokesh
38

Saya datang terlambat ke utas ini, dan saya terkejut bahwa tidak ada yang menyebutkan Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
Alex D
sumber
2
Saya terkejut bahwa Anda adalah orang pertama yang mereferensikan kelas Metode secara eksplisit. Lain harta kurang dikenal diperkenalkan di 1,9: Method#parameters.
fny
13

Menyalin jawaban saya dari pertanyaan serupa yang lebih baru yang menambahkan informasi baru ke masalah ini.

Ruby 1.9 memiliki metode yang disebut source_location :

Mengembalikan nama file sumber Ruby dan nomor baris yang mengandung metode ini atau nihil jika metode ini tidak didefinisikan dalam Ruby (yaitu asli)

Ini telah di-backport ke 1.8.7 oleh permata ini:

Jadi, Anda dapat meminta metode ini:

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

Dan kemudian meminta source_locationmetode itu:

m.source_location

Ini akan mengembalikan array dengan nama file dan nomor baris. Misalnya untuk ActiveRecord::Base#validatespengembalian ini:

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]

Untuk kelas dan modul, Ruby tidak menawarkan dukungan bawaan, tetapi ada inti yang sangat baik di luar sana yang dibangun source_locationuntuk mengembalikan file untuk metode yang diberikan atau file pertama untuk kelas jika tidak ada metode yang ditentukan:

Beraksi:

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]

Pada Mac dengan TextMate terinstal, ini juga muncul editor di lokasi yang ditentukan.

Laas
sumber
7

Ini mungkin membantu tetapi Anda harus membuat kode sendiri. Disisipkan dari blog:

Ruby memberikan callback method_added () yang dipanggil setiap kali metode ditambahkan atau didefinisikan ulang dalam kelas. Itu adalah bagian dari kelas Modul, dan setiap Kelas adalah Modul. Ada juga dua panggilan balik terkait yang disebut method_removed () dan method_undefined ().

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

Ken
sumber
6

Jika Anda bisa crash metode, Anda akan mendapatkan backtrace yang akan memberi tahu Anda persis di mana itu.

Sayangnya, jika Anda tidak dapat menghentikannya maka Anda tidak dapat mengetahui di mana itu telah ditetapkan. Jika Anda mencoba menggunakan metode ini dengan menimpa atau menimpanya, maka setiap kerusakan akan datang dari metode Anda yang ditimpa atau diganti, dan itu tidak akan ada gunanya.

Cara-cara bermanfaat untuk metode mogok:

  1. Lulus di nilmana ia melarangnya - banyak waktu metode ini akan menaikkan ArgumentErroratau selalu ada NoMethodErrorpada kelas nil.
  2. Jika Anda memiliki pengetahuan orang dalam tentang metode ini, dan Anda tahu bahwa metode itu pada gilirannya memanggil beberapa metode lain, maka Anda dapat menimpa metode yang lain, dan meningkatkannya.
Orion Edwards
sumber
Jika Anda memiliki akses ke kode tersebut, Anda dapat dengan mudah memasukkan require 'ruby-debug'; debugger kode Anda di mana Anda ingin memasukkannya .
the Tin Man
"Sayangnya, jika kamu tidak bisa crash itu maka kamu tidak bisa mengetahui di mana itu telah didefinisikan." Tidak benar. Se jawaban lain.
Martin T.
6

Mungkin #source_locationbisa membantu menemukan dari mana metode ini berasal.

ex:

ModelName.method(:has_one).source_location

Kembali

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

ATAU

ModelName.new.method(:valid?).source_location

Kembali

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
Samda
sumber
4

Jawaban yang sangat terlambat :) Tapi jawaban sebelumnya tidak membantu saya

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
cekcok
sumber
Kenapa kamu lewat nil?
Arup Rakshit
@ArupRakshit dari dokumentasi: «Menetapkan proc sebagai pengendali untuk melacak, atau menonaktifkan pelacakan jika parameternya adalah nil
tig
3

Anda mungkin dapat melakukan sesuatu seperti ini:

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

Kemudian pastikan foo_finder dimuat pertama kali dengan sesuatu seperti

ruby -r foo_finder.rb railsapp

(Saya hanya mengacaukan rel, jadi saya tidak tahu persis, tapi saya membayangkan ada cara untuk memulainya seperti ini.)

Ini akan menunjukkan kepada Anda semua definisi ulang dari String # foo. Dengan sedikit pemrograman meta, Anda dapat menggeneralisasi untuk fungsi apa pun yang Anda inginkan. Tapi itu perlu dimuat SEBELUM file yang benar-benar melakukan definisi ulang.

ASHelly
sumber
3

Anda selalu bisa mendapatkan backtrace dari tempat Anda berada dengan menggunakan caller().

Manusia Timah
sumber
1
Ini berguna untuk menemukan apa yang memanggil Anda, tetapi tidak baik ketika Anda mencoba menemukan di mana sesuatu didefinisikan.
Manusia Timah