2010-02-03 37 views
7

Tôi đang cố gắng gọi lại khi có bất kỳ phương thức nào trên một lớp cụ thể được gọi. Ghi đè "gửi" không hoạt động. Có vẻ như gửi không được gọi trong lời gọi phương thức Ruby bình thường. Lấy ví dụ sau.Cuộc gọi phương thức ghi đè trong Ruby?

class Test 
    def self.items 
    @items ||= [] 
    end 
end 

Nếu chúng tôi ghi đè gửi vào Kiểm tra, sau đó gọi Test.items, gửi không được gọi.

Tôi đang cố gắng làm điều gì?

Tôi không muốn sử dụng set_trace_func, vì nó có thể làm chậm đáng kể mọi thứ.

+1

Có thể thực hiện điều này bằng cách đánh dấu lớp học thành lớp của riêng tôi và ủy quyền các cuộc gọi phương thức đến lớp gốc. –

+1

Protip: Tất cả các câu trả lời cho bạn biết cách ghi đè lên một phương thức duy nhất cũng cho bạn biết cách thực hiện nó cho tất cả các phương thức mà một lớp thực hiện. Bạn chỉ cần lặp lại chúng. Đó có phải là câu hỏi thực sự của bạn? – Chuck

Trả lời

12

Sử dụng alias hoặc alias_method:

# the current implementation of Test, defined by someone else 
# and for that reason we might not be able to change it directly 
class Test 
    def self.items 
    @items ||= [] 
    end 
end 

# we open the class again, probably in a completely different 
# file from the definition above 
class Test 
    # open up the metaclass, methods defined within this block become 
    # class methods, just as if we had defined them with "def self.my_method" 
    class << self 
    # alias the old method as "old_items" 
    alias_method :old_items, :items 
    # redeclare the method -- this replaces the old items method, 
    # but that's ok since it is still available under it's alias "old_items" 
    def items 
     # do whatever you want 
     puts "items was called!" 
     # then call the old implementation (make sure to call it last if you rely 
     # on its return value) 
     old_items 
    end 
    end 
end 

Tôi viết lại mã của bạn bằng cách sử dụng cú pháp class << self để mở ra các metaclass, bởi vì tôi không chắc chắn làm thế nào để sử dụng alias_method về phương pháp lớp khác.

+2

Không, tôi muốn gọi lại để gọi phương thức * any *. Các mục chỉ là một ví dụ. –

+0

Vì vậy, hãy làm điều đó với tất cả các phương pháp của bạn. – Chuck

+1

Kết hợp với ': method_added' nó sẽ làm những gì bạn cần. – Theo

0

Tôi không có một câu trả lời hoàn chỉnh, nhưng tôi nghĩ method_added có thể hữu ích ở đây.

2

Bạn có thể xem cách thực hiện điều này thông qua chức năng móc ExtLib. ExtLib::Hook về cơ bản cho phép bạn gọi các cuộc gọi lại tùy ý trước hoặc sau khi phương thức hoàn tất. Xem mã trên GitHub tại đây để biết cách thực hiện nó (nó ghi đè :method_added để tự động viết lại các phương thức khi chúng được thêm vào lớp).

4

Something như thế này: làm việc với các phương pháp và phương pháp dụ lớp, nó sẽ không chỉ đánh chặn các phương pháp hiện tại được định nghĩa trong lớp nhưng bất kỳ được thêm vào sau mặc dù mở lại lớp vv

(có cũng rcapture http://code.google.com/p/rcapture/):

module Interceptor 
    def intercept_callback(&block) 
    @callback = block 
    @old_methods = {} 
    end 
    def method_added(my_method) 
    redefine self, self, my_method, instance_method(my_method) 
    end 
    def singleton_method_added(my_method) 
    meta = class << self; self; end 
    redefine self, meta, my_method, method(my_method) 
    end 
    def redefine(klass, me, method_name, my_method) 
    return unless @old_methods and not @old_methods.include? method_name 
    @old_methods[method_name] = my_method 
    me.send :define_method, method_name do |*args| 
     callback = klass.instance_variable_get :@callback 
     orig_method = klass.instance_variable_get(:@old_methods)[method_name] 
     callback.call *args if callback 
     orig_method = orig_method.bind self if orig_method.is_a? UnboundMethod 
     orig_method.call *args 
    end 
    end 
end 

class Test 
    extend Interceptor 
    intercept_callback do |*args| 
    puts 'was called' 
    end 
    def self.items 
    puts "items" 
    end 
    def apple 
    puts "apples" 
    end 
end 

class Test 
    def rock 
    puts "rock" 
    end 
end 

Test.items 
Test.new.apple 
Test.new.rock 
+0

Thậm chí hữu ích hơn nếu bạn chuyển 'method_name' và' caller' đến cuộc gọi lại trước hoặc thay vì '* args', và sau đó nói' intercept_callback do | method_name, by_caller | ; đặt "# {method_name} được gọi bởi # {by_caller.first}"; end' –

1

Bạn có thể làm điều gì đó như thế này, thậm chí bạn có thể đặt điều kiện cho phương thức được gọi hoặc không (tôi không nghĩ điều đó hữu ích, nhưng bạn vẫn có trong trường hợp đó).

module MethodInterceptor 

    def self.included(base) 
    base.extend(ClassMethods) 
    base.send(:include, InstanceMethods) 
    base.class_eval do 
     # we declare the method_list on the class env 
     @_instance_method_list = base.instance_methods.inject(Hash.new) do |methods, method_name| 
     # we undef all methods 
     if !%w(__send__ __id__ method_missing class).include?(method_name) 
      methods[method_name.to_sym] = base.instance_method(method_name) 
      base.send(:undef_method, method_name) 
     end 
     methods 
     end 
    end 
    end 

    module ClassMethods 

    def _instance_method_list 
     @_instance_method_list 
    end 

    def method_added(name) 
     return if [:before_method, :method_missing].include?(name) 
     _instance_method_list[name] = self.instance_method(name) 
     self.send(:undef_method, name) 
     nil 
    end 

    end 

    module InstanceMethods 

    def before_method(method_name, *args) 
     # by defaults it always will be called 
     true 
    end 

    def method_missing(name, *args) 
     if self.class._instance_method_list.key?(name) 
     if before_method(name, *args) 
      self.class._instance_method_list[name].bind(self).call(*args) 
     else 
      super 
     end 
     else 
     super 
     end 
    end 
    end 

end 

class Say 
    include MethodInterceptor 

    def before_method(method_name, *args) 
    # you cannot say hello world! 
    return !(method_name == :say && args[0] == 'hello world') 
    end 

    def say(msg) 
    puts msg 
    end 

end 

Hy vọng công trình này sẽ hoạt động.

0

Tôi đã làm cho nó hoạt động bằng cách sử dụng một lớp Proxy - và sau đó thiết lập một hằng số bằng cách sử dụng tên của lớp thực. Tôi không chắc chắn làm thế nào để làm cho nó làm việc với các trường hợp mặc dù. Có cách nào thay đổi biến đối tượng nào đang trỏ tới không?

Về cơ bản, tôi muốn làm điều này:

t = Test.new 
Persist.new(t) 

t.foo # invokes callback 

Dưới đây là đoạn code tôi sử dụng để làm cho nó làm việc với các lớp:

class Persist 
    class Proxy 
    instance_methods.each { |m| 
     undef_method m unless m =~ /(^__|^send$|^object_id$)/ 
    } 

    def initialize(object) 
     @_persist = object 
    end 

    protected 
     def method_missing(sym, *args) 
     puts "Called #{sym}" 
     @_persist.send(sym, *args) 
     end 
    end 


    attr_reader :object, :proxy 

    def initialize(object) 
    @object = object 
    @proxy = Proxy.new(@object) 
    if object.respond_to?(:name) 
     silence_warnings do 
     Object.const_set(@object.name, @proxy) 
     end 
    end 
    end 
end 
+0

Trên thực tế, phương pháp suy nghĩ lại định nghĩa lại có thể là cách để đi - nó cũng sẽ hoạt động với các trường hợp: https://gist.github.com/5226decf57adc11baf46 –

1

được bạn cố gắng để treo một phương pháp thể hiện của một lớp học? Sau đó, đoạn mã sau có thể hữu ích.Nó sử dụng RCapture có thể được cài đặt thông qua

gem install rcapture 

Một bài viết introductionary có thể được tìm thấy tại here

require 'rcapture' 

class Test 
    include RCapture::Interceptable 
end 

Test.capture_post :class_methods => :items do 
    puts "items!" 
end 

Test.items 
#=> items! 
+0

từ nhà phát triển RCapture nổi tiếng! – martinus

0

cách tiếp cận của tôi để điều này sẽ được quấn đối tượng Tôi đang cố gắng để đăng nhập với một vỏ Logger đối tượng chỉ đơn giản gọi lại đối tượng ban đầu. Mã bên dưới hoạt động bằng cách gói đối tượng bạn muốn đăng nhập với một lớp đơn giản gọi bất kỳ phương thức nào bạn muốn trên đối tượng bên dưới, nhưng cung cấp cách bẫy các cuộc gọi đó và đăng nhập (hoặc bất kỳ) nào cho mỗi sự kiện truy cập ..

class Test 
    def self.items 
    puts " Class Items run" 
    "Return" 
    end 

    def item 
    puts " Instance item run" 
    return 47, 11 
    end 
end 

class GenericLogger 
    @@klass = Object # put the class you want to log into @@klass in a sub-class 
    def initialize(*args) 
    @instance = @@klass.new(*args) 
    end 
    def self.method_missing(meth, *args, &block) 
    retval = handle_missing(@@klass, meth, *args, &block) 
    if !retval[0] 
     super 
    end 
    retval[1] 
    end 

    def method_missing(meth, *args, &block) 
    retval = self.class.handle_missing(@instance, meth, *args, &block) 
    if !retval[0] 
     super 
    end 
    retval[1] 
    end 

    def self.handle_missing(obj, meth, *args, &block) 
    retval = nil 
    if obj.respond_to?(meth.to_s) 
     # PUT YOUR LOGGING CODE HERE 
     if obj.class.name == "Class" 
     puts "Logger code run for #{obj.name}.#{meth.to_s}" 
     else 
     puts "Logger code run for instance of #{obj.class.name}.#{meth.to_s}" 
     end 
     retval = obj.send(meth, *args) 
     return true, retval 
    else 
     return false, retval 
    end 
    end 
end 

# When you want to log a class, create one of these sub-classes 
# and place the correct class you are logging in @@klass 
class TestLogger < GenericLogger 
    @@klass = Test 
end 

retval = TestLogger.items 
puts "Correctly handles return values: #{retval}" 
tl = TestLogger.new 
retval = tl.item 
puts "Correctly handles return values: #{retval}" 

begin 
    tl.itemfoo 
rescue NoMethodError => e 
    puts "Correctly fails with unknown methods for instance of Test:" 
    puts e.message 
end 

begin 
    TestLogger.itemsfoo 
rescue NoMethodError => e 
    puts "Correctly fails with unknown methods for class Test" 
    puts e.message 
end 

Output từ đó mẫu mã là:

Logger code run for Test.items 
    Class Items run 
Correctly handles return values: Return 
Logger code run for instance of Test.item 
    Instance item run 
Correctly handles return values: [47, 11] 
Correctly fails with unknown methods for instance of Test: 
undefined method `itemfoo' for #<TestLogger:0x2962038 @instance=#<Test:0x2962008>> 
Correctly fails with unknown methods for class Test 
undefined method `itemsfoo' for TestLogger:Class 
0

singleton_method_added có thể cung cấp cho bạn một giải pháp đơn giản:

class Item 
    @added_methods = [] 
    class << self 
    def singleton_method_added name 
     if name != :singleton_method_added && [email protected]_methods.include?(name) 
     @added_methods << name 
     pMethod = self.singleton_method name 
     self.singleton_class.send :define_method, name do |*args, &blk| 
     puts "Callback functions calling..." 
     pMethod.call(*args, &blk) 
     end 
    end 
    end 

    def speak 
    puts "This is #{self}" 
    end 
end 

Hy vọng điều này sẽ giúp.

Các vấn đề liên quan