วิธีการค้นหาที่กำหนดวิธีการที่รันไทม์?


328

เมื่อเร็ว ๆ นี้เรามีปัญหาที่เกิดขึ้นหลังจากที่มีการกระทำหลายอย่างเกิดขึ้นกระบวนการแบ็กเอนด์ไม่สามารถทำงานได้ ตอนนี้เราเป็นเด็กชายและเด็กหญิงตัวเล็ก ๆ ที่ดีและวิ่งrake testตามเช็คอินทุกครั้ง แต่เนื่องจากมีสิ่งแปลกประหลาดบางอย่างในการโหลดห้องสมุดของ Rails มันเกิดขึ้นเมื่อเราวิ่งจาก Mongrel โดยตรงในโหมดการผลิต

ฉันติดตามข้อผิดพลาดและเป็นเพราะ Rails gem ใหม่เขียนทับวิธีในคลาส String ในวิธีที่ใช้งานได้ไม่ดีในโค้ด Rails

ยังไงก็ตามเรื่องสั้นสั้น ๆ มีวิธีที่รันไทม์เพื่อถามทับทิมที่กำหนดวิธีการ? สิ่งที่ต้องการwhereami( :foo )กลับมา/path/to/some/file.rb line #45? ในกรณีนี้การบอกฉันว่ามันถูกกำหนดไว้ในคลาส String จะไม่ช่วยเหลือเพราะห้องสมุดบางแห่งทำงานหนักเกินไป

ฉันไม่สามารถรับประกันได้ว่าแหล่งข้อมูลอาศัยอยู่ในโครงการของฉันดังนั้นการโลภเพื่อ'def foo'ไม่จำเป็นต้องให้สิ่งที่ฉันต้องการไม่ต้องพูดถึงถ้าฉันมีหลายคน def fooบางครั้งฉันก็ไม่รู้จนกระทั่งถึงรันไทม์ที่ฉันอาจใช้


1
ใน Ruby 1.8.7 มีการเพิ่มวิธีพิเศษเพื่อค้นหาข้อมูลนี้โดยเฉพาะ (และยังคงมีอยู่ใน 1.9.3) ... รายละเอียดในคำตอบของฉันด้านล่าง
Alex D

คำตอบ:


419

นี่มันสายไปแล้ว แต่นี่คือวิธีที่คุณจะพบว่ามีการกำหนดวิธีการไว้อย่างไร:

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>

หากคุณใช้ Ruby 1.9+ คุณสามารถใช้ 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]

โปรดทราบว่าสิ่งนี้จะไม่ทำงานกับทุกอย่างเช่นรหัสที่คอมไพล์แล้ว ระดับวิธีที่มีฟังก์ชั่นบางอย่างมากเกินไปเช่นเจ้าของ # วิธีซึ่งส่งไฟล์ที่วิธีการที่ถูกกำหนดไว้

แก้ไข: ดู__file__และ__line__และหมายเหตุสำหรับ REE ในคำตอบอื่น ๆ พวกเขาก็มีประโยชน์เช่นกัน - น้ำหนัก


1
source_location ดูเหมือนจะทำงานกับ 1.8.7-p334 โดยใช้ activesupport-2.3.14
Jeff Maass

หลังจากค้นหาวิธีการให้ลองใช้วิธีการของownerวิธีการ
Juguang

1
หมายเลขสอง2.method(:crime)คืออะไร?
stack1

1
ตัวอย่างของคลาสFixnum
kitteehh

1
หมายเหตุสำคัญ: สิ่งนี้จะไม่ดึงวิธีการที่กำหนดแบบไดนามิกใดmethod_missingๆ ดังนั้นหากคุณมีโมดูลหรือคลาสบรรพบุรุษที่มีclass_evalหรือdefine_methodภายในmethod_missingแล้ววิธีนี้จะไม่ทำงาน
cdpalmer

83

คุณสามารถไปได้ไกลกว่าโซลูชันข้างต้นเล็กน้อย สำหรับ Ruby 1.8 Enterprise Edition มี__file__และ__line__วิธีการใน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

สำหรับ Ruby 1.9 ขึ้นไปมีsource_location(ขอบคุณ 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]

2
ฉันจะได้รับ "NoMethodError: วิธีการที่ไม่ได้กำหนด" สำหรับทั้งสอง__file__และ__line__ที่ใด ๆเช่นชั้นอดีต:Method method(:method).__file__
Vikrant Chaudhary

คุณมีทับทิมรุ่นไหน
James Adam

ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Vikrant Chaudhary

19
เมื่อวันที่ทับทิม 1.9 m.__file__และได้ถูกแทนที่ด้วยm.__line__ m.source_location
Jonathan Tran

แล้ว Ruby 2.1 ล่ะ?
Lokesh

38

Method#ownerฉันมาสายไปหัวข้อนี้และฉันประหลาดใจที่ไม่มีใครกล่าวถึง

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

2
ฉันประหลาดใจที่คุณเป็นคนแรกที่อ้างอิงคลาสเมธอดอย่างชัดเจน อีกสมบัติที่รู้จักกันน้อยแนะนำใน Method#parameters1.9:
fny

13

คัดลอกคำตอบของฉันจากคำถามที่คล้ายกันที่ใหม่กว่าซึ่งเพิ่มข้อมูลใหม่ให้กับปัญหานี้

Ruby 1.9มีวิธีการที่เรียกว่าsource_location :

ส่งกลับชื่อไฟล์แหล่งที่มาของทับทิมและหมายเลขบรรทัดที่มีวิธีการนี้หรือไม่มีถ้าวิธีนี้ไม่ได้กำหนดไว้ในทับทิม (เช่นพื้นเมือง)

สิ่งนี้ได้รับการ backported 1.8.7โดยอัญมณีนี้:

ดังนั้นคุณสามารถขอวิธีการ:

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

แล้วขอsource_locationวิธีการนั้น

m.source_location

นี่จะส่งคืนอาร์เรย์ที่มีชื่อไฟล์และหมายเลขบรรทัด เช่นสำหรับActiveRecord::Base#validatesผลตอบแทนนี้:

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]

สำหรับคลาสและโมดูลนั้น Ruby ไม่ได้ให้การสนับสนุนในตัว แต่มี Gist ที่ยอดเยี่ยมซึ่งจะสร้างsource_locationเพื่อส่งคืนไฟล์สำหรับวิธีการที่กำหนดหรือไฟล์แรกสำหรับคลาสหากไม่มีวิธีที่ระบุไว้:

ในการดำเนินการ:

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]

บน Mac ที่ติดตั้ง TextMate สิ่งนี้จะปรากฏตัวแก้ไขในตำแหน่งที่ระบุ


7

สิ่งนี้อาจช่วยได้ แต่คุณจะต้องเขียนรหัสด้วยตัวเอง วางจากบล็อก:

Ruby ให้การเรียกกลับ method_added () ที่ถูกเรียกทุกครั้งที่มีการเพิ่มวิธีการหรือกำหนดใหม่ภายในชั้นเรียน เป็นส่วนหนึ่งของคลาส Module และทุกคลาสเป็นโมดูล นอกจากนี้ยังมีการเรียกกลับที่เกี่ยวข้องสองรายการที่เรียกว่า method_removed () และ method_undefined ()

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


6

หากคุณทำผิดพลาดได้คุณจะได้รับ backtrace ซึ่งจะบอกคุณว่ามันอยู่ตรงไหน

น่าเสียดายที่ถ้าคุณไม่สามารถทำผิดพลาดได้คุณจะไม่สามารถทราบได้ว่ามันถูกกำหนดไว้ที่ไหน หากคุณพยายามลิงด้วยวิธีการโดยการเขียนทับหรือเอาชนะมันความผิดพลาดใด ๆ จะมาจากวิธีการเขียนทับหรือการเขียนทับของคุณและจะไม่ใช้งานใด ๆ

วิธีการที่เป็นประโยชน์ของวิธีการขัดข้อง:

  1. ผ่านnilที่ห้ามมัน - หลายครั้งที่วิธีการที่จะยกระดับArgumentErrorหรือเป็นปัจจุบันNoMethodErrorในระดับศูนย์
  2. หากคุณมีความรู้เกี่ยวกับวิธีการและคุณรู้ว่าวิธีการในการเรียกวิธีการอื่น ๆ แล้วคุณสามารถเขียนทับวิธีอื่น ๆ และเพิ่มภายในที่

หากคุณมีสิทธิ์เข้าถึงรหัสคุณสามารถใส่require 'ruby-debug'; debugger รหัสของคุณลงไปในที่ที่คุณต้องการได้อย่างง่ายดาย
The Tin Man

"น่าเสียดายถ้าคุณไม่สามารถทำมันได้คุณจะไม่สามารถทราบได้ว่ามันถูกกำหนดไว้ที่ไหน" มันไม่จริง. ดูคำตอบอื่น ๆ
Martin T.

6

อาจจะ#source_locationสามารถช่วยในการค้นหาวิธีการที่มาจาก

อดีต:

ModelName.method(:has_one).source_location

กลับ

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

หรือ

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

กลับ

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

4

คำตอบที่ล่าช้ามาก :) แต่คำตอบก่อนหน้านี้ไม่ได้ช่วยฉัน

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

ทำไมคุณจะผ่านnil?
Arup Rakshit

@ArupRakshit จากเอกสาร: «สร้าง proc เป็นตัวจัดการสำหรับการติดตามหรือปิดใช้งานการติดตามหากพารามิเตอร์เป็นnil»
tig

3

คุณอาจทำสิ่งนี้ได้:

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

จากนั้นตรวจสอบให้แน่ใจว่าได้โหลด foo_finder ก่อนด้วยบางสิ่ง

ruby -r foo_finder.rb railsapp

(ฉันแค่ยุ่งกับรางดังนั้นฉันไม่รู้เหมือนกัน แต่ฉันคิดว่ามีวิธีที่จะเริ่มมันเช่นนี้)

สิ่งนี้จะแสดงให้คุณเห็นข้อกำหนดใหม่ทั้งหมดของ String # foo ด้วยการเขียนโปรแกรมเมตาเล็กน้อยคุณสามารถทำให้เป็นมาตรฐานสำหรับฟังก์ชั่นที่คุณต้องการ แต่ไม่จำเป็นต้องโหลดก่อนไฟล์ที่จะทำการกำหนดใหม่


3

caller()คุณสามารถได้รับการติดตามย้อนหลังคุณจะอยู่ที่ไหนโดยใช้


1
สิ่งนี้มีประโยชน์ในการค้นหาสิ่งที่เรียกว่าคุณ แต่ไม่ดีเมื่อคุณกำลังค้นหาสิ่งที่กำหนดไว้
ชายดีบุก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.