Làm thế nào để tìm nơi một phương thức được xác định trong thời gian chạy?


327

Gần đây chúng tôi đã có một vấn đề trong đó, sau khi một loạt các cam kết đã xảy ra, một quá trình phụ trợ không chạy được. Bây giờ, chúng tôi là những cậu bé và cô bé ngoan và chạy rake testtheo sau mỗi lần đăng ký, nhưng do một số điều kỳ lạ trong việc tải thư viện của Rails, nó chỉ xảy ra khi chúng tôi chạy nó trực tiếp từ Mongrel trong chế độ sản xuất.

Tôi đã theo dõi lỗi và đó là do đá quý Rails mới ghi đè một phương thức trong lớp String theo cách phá vỡ một sử dụng hẹp trong mã Rails thời gian chạy.

Dù sao, câu chuyện dài, có cách nào, trong thời gian chạy, để hỏi Ruby nơi một phương thức đã được xác định? Một cái gì đó như thế whereami( :foo )trở lại /path/to/some/file.rb line #45? Trong trường hợp này, nói với tôi rằng nó được định nghĩa trong Chuỗi lớp sẽ không có ích, bởi vì nó bị quá tải bởi một số thư viện.

Tôi không thể đảm bảo nguồn sống trong dự án của mình, vì vậy, việc grepping 'def foo'không nhất thiết phải cung cấp cho tôi những gì tôi cần, chưa kể nếu tôi có nhiều def foo , đôi khi tôi không biết cho đến khi nào tôi có thể sử dụng.


1
Trong Ruby 1.8.7, một phương pháp đặc biệt đã được thêm vào cụ thể để tìm thông tin này (và nó vẫn còn ở 1.9.3) ... chi tiết trong câu trả lời của tôi dưới đây.
Alex D

Câu trả lời:


418

Điều này thực sự muộn, nhưng đây là cách bạn có thể tìm thấy nơi một phương thức được xác định:

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>

Nếu bạn đang dùng Ruby 1.9+, bạn có thể sử dụng 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]

Lưu ý rằng điều này sẽ không hoạt động trên tất cả mọi thứ, như mã được biên dịch riêng. Lớp Phương thức cũng có một số hàm gọn gàng, như chủ sở hữu Phương thức # trả về tệp nơi phương thức được xác định.

EDIT: Cũng xem __file____line__và ghi chú cho REE trong câu trả lời khác, chúng cũng tiện dụng. - wg


1
source_location dường như hoạt động cho 1.8.7-p334 bằng cách sử dụng activesupport-2.3,14
Jeff Maass

sau khi tìm thấy phương thức, hãy thử phương pháp của ownerPhương pháp
Juguang

1
Số hai trong là 2.method(:crime)gì?
stack1

1
một ví dụ của lớp họcFixnum
kitteehh

1
Lưu ý quan trọng: Điều này sẽ không kéo lên bất kỳ phương thức xác định động nào từ method_missing. Vì vậy, nếu bạn có một mô-đun hoặc lớp tổ tiên có class_evalhoặc define_methodbên trong method_missing, thì phương thức này sẽ không hoạt động.
cdpalmer

82

Bạn thực sự có thể đi xa hơn một chút so với giải pháp trên. Đối với Ruby 1.8 Enterprise Edition, có các phương thức __file____line__phương thức 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

Đối với Ruby 1.9 và hơn thế nữa, có source_location(cảm ơn 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
Tôi nhận được "NoMethodError: phương thức không xác định" cho cả __file____line__trên bất kỳ Methodtrường hợp lớp nào , ví dụ : method(:method).__file__.
Vikrant Chaudhary

Phiên bản nào của ruby?
James Adam

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

19
Trên Ruby 1.9, m.__file__m.__line__đã được thay thế bằng m.source_location.
Jonathan Trần

Còn Ruby 2.1 thì sao?
Lokesh

37

Tôi đến muộn với chủ đề này, và ngạc nhiên rằng không ai đề cập đến Method#owner.

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

2
Tôi ngạc nhiên khi bạn là người đầu tiên tham chiếu lớp Phương thức một cách rõ ràng. Một kho báu khác ít được biết đến được giới thiệu trong 1.9 : Method#parameters.
fny

12

Sao chép câu trả lời của tôi từ một câu hỏi tương tự mới hơn có thêm thông tin mới cho vấn đề này.

Ruby 1.9 có phương thức gọi là source_location :

Trả về tên tệp và số dòng của Ruby chứa phương thức này hoặc nil nếu phương thức này không được xác định trong Ruby (tức là bản địa)

Điều này đã được đưa vào bản 1.8.7 bởi viên ngọc này:

Vì vậy, bạn có thể yêu cầu phương thức:

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

Và sau đó yêu cầu source_locationphương pháp đó:

m.source_location

Điều này sẽ trả về một mảng với tên tệp và số dòng. Ví dụ cho ActiveRecord::Base#validateslợi nhuận này:

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]

Đối với các lớp và mô-đun, Ruby không cung cấp hỗ trợ tích hợp, nhưng có một Gist xuất sắc ngoài đó xây dựng source_locationđể trả về tệp cho một phương thức nhất định hoặc tệp đầu tiên cho một lớp nếu không có phương thức nào được chỉ định:

Trong hành động:

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]

Trên máy Mac có cài đặt TextMate, điều này cũng bật lên trình chỉnh sửa tại vị trí đã chỉ định.


6

Điều này có thể giúp nhưng bạn sẽ phải tự viết mã. Dán từ blog:

Ruby cung cấp một hàm gọi lại method_added () được gọi mỗi khi một phương thức được thêm hoặc định nghĩa lại trong một lớp. Đó là một phần của lớp Mô-đun và mỗi Lớp là một Mô-đun. Ngoài ra còn có hai cuộc gọi lại liên quan được gọi là method_remond () và method_und xác định ().

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


5

Nếu bạn có thể phá vỡ phương thức, bạn sẽ nhận được một backtrace sẽ cho bạn biết chính xác vị trí của nó.

Thật không may, nếu bạn không thể phá vỡ nó thì bạn không thể tìm ra nơi nó đã được xác định. Nếu bạn cố gắng sử dụng phương thức này bằng cách ghi đè lên nó hoặc ghi đè lên nó, thì mọi sự cố sẽ đến từ phương thức ghi đè hoặc ghi đè của bạn và nó sẽ không được sử dụng.

Những cách hữu ích của phương pháp sụp đổ:

  1. Vượt qua nilnơi nó cấm - rất nhiều thời gian phương thức sẽ đưa ra một ArgumentErrorhoặc hiện tại NoMethodErrortrên một lớp không.
  2. Nếu bạn có kiến ​​thức bên trong về phương thức và bạn biết rằng phương thức lần lượt gọi một số phương thức khác, thì bạn có thể ghi đè lên phương thức khác và nâng cao bên trong phương thức đó.

Nếu bạn có quyền truy cập vào mã, bạn có thể dễ dàng chèn require 'ruby-debug'; debugger mã của mình vào nơi bạn muốn đăng nhập.
Tin Man

"Thật không may, nếu bạn không thể phá vỡ nó thì bạn không thể tìm ra nơi nó đã được xác định." Là không đúng sự thật. Se câu trả lời khác.
Martin T.

5

Có lẽ #source_locationcó thể giúp tìm ra phương pháp đến từ đâu.

Ví dụ:

ModelName.method(:has_one).source_location

Trở về

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

HOẶC LÀ

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

Trở về

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

3

Câu trả lời rất muộn :) Nhưng những câu trả lời trước đó không giúp tôi

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

Tại sao bạn đi qua nil?
Arup Rakshit

@ArupRakshit từ tài liệu: «Thiết lập Proc làm trình xử lý để theo dõi hoặc vô hiệu hóa theo dõi nếu tham số là nil
tig

2

Bạn có thể làm một cái gì đó như thế này:

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

Sau đó, đảm bảo foo_finder được tải trước với nội dung như

ruby -r foo_finder.rb railsapp

(Tôi chỉ bị rối với đường ray, vì vậy tôi không biết chính xác, nhưng tôi tưởng tượng có một cách để bắt đầu nó giống như thế này.)

Điều này sẽ cho bạn thấy tất cả các định nghĩa lại của String # foo. Với một chút lập trình meta, bạn có thể khái quát nó cho bất kỳ chức năng nào bạn muốn. Nhưng nó cần phải được tải TRƯỚC KHI tập tin thực sự định nghĩa lại.


2

Bạn luôn có thể nhận được một backtrace về nơi bạn đang sử dụng caller().


1
Điều này rất hữu ích để tìm thấy những gì được gọi là bạn, nhưng không tốt khi bạn đang cố gắng tìm nơi được xác định.
Tin Man
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.