2010-09-23 15 views
5

मैं रूबी-क्लास पर विधि कॉल को रोकना चाहता हूं और विधि के वास्तविक निष्पादन से पहले और बाद में कुछ करने में सक्षम होना चाहता हूं। मैं निम्नलिखित कोड की कोशिश की है, लेकिन त्रुटि मिलती है:रूबी विधि अवरोध

MethodInterception.rb:16:in before_filter': (eval):2:in alias_method': undefined method say_hello' for class HomeWork' (NameError) from (eval):2:in `before_filter'

किसी को भी मुझे इसे सही करने के लिए मदद कर सकते हैं?

class MethodInterception 

    def self.before_filter(method) 
    puts "before filter called" 
    method = method.to_s 
    eval_string = " 
     alias_method :old_#{method}, :#{method} 

     def #{method}(*args) 
     puts 'going to call former method' 
     old_#{method}(*args) 
     puts 'former method called' 
     end 
    " 
    puts "going to call #{eval_string}" 
    eval(eval_string) 
    puts "return" 
    end 
end 

class HomeWork < MethodInterception 
    before_filter(:say_hello) 

    def say_hello 
    puts "say hello" 
    end 

end 

उत्तर

2

मूल से कम कोड बदल दिया गया था। मैंने केवल 2 लाइन संशोधित की।

class MethodInterception 

    def self.before_filter(method) 
    puts "before filter called" 
    method = method.to_s 
    eval_string = " 
     alias_method :old_#{method}, :#{method} 

     def #{method}(*args) 
     puts 'going to call former method' 
     old_#{method}(*args) 
     puts 'former method called' 
     end 
    " 
    puts "going to call #{eval_string}" 
    class_eval(eval_string) # <= modified 
    puts "return" 
    end 
end 

class HomeWork < MethodInterception 

    def say_hello 
    puts "say hello" 
    end 

    before_filter(:say_hello) # <= change the called order 
end 

यह अच्छी तरह से काम करता है।

HomeWork.new.say_hello 
#=> going to call former method 
#=> say hello 
#=> former method called 
14

मैं सिर्फ इस के साथ आया था:

module MethodInterception 
    def method_added(meth) 
    return unless (@intercepted_methods ||= []).include?(meth) && [email protected] 

    @recursing = true # protect against infinite recursion 

    old_meth = instance_method(meth) 
    define_method(meth) do |*args, &block| 
     puts 'before' 
     old_meth.bind(self).call(*args, &block) 
     puts 'after' 
    end 

    @recursing = nil 
    end 

    def before_filter(meth) 
    (@intercepted_methods ||= []) << meth 
    end 
end 

इतना है कि यह प्रयोग करें:

class HomeWork 
    extend MethodInterception 

    before_filter(:say_hello) 

    def say_hello 
    puts "say hello" 
    end 
end 

काम करता है:

HomeWork.new.say_hello 
# before 
# say hello 
# after 

अपने कोड में बुनियादी समस्या यह है कि था आपने अपनामें विधि का नाम बदल दिया है 10 विधि, लेकिन फिर आपके क्लाइंट कोड में, विधि को वास्तव में परिभाषित करने से पहले before_filter कहा जाता है, जिसके परिणामस्वरूप एक विधि का नाम बदलने का प्रयास होता है जो मौजूद नहीं है।

समाधान सरल है: ऐसा नहीं करें ™!

ठीक है, ठीक है, शायद इतना आसान नहीं है। आप के बाद before_filterको हमेशा कॉल करने के लिए अपने ग्राहकों को मजबूर कर सकते हैं, उन्होंने अपनी विधियों को परिभाषित किया है। हालांकि, यह बुरा एपीआई डिजाइन है।

तो, आपको किसी भी तरह से विधि के लपेटने को रोकने के लिए अपने कोड की व्यवस्था करना होगा जब तक यह वास्तव में मौजूद न हो। और मैंने यही किया: before_filter विधि के अंदर विधि को फिर से परिभाषित करने के बजाय, मैं केवल इस तथ्य को रिकॉर्ड करता हूं कि इसे बाद में फिर से परिभाषित किया जाना है। फिर, मैं वास्तविकmethod_added हुक में फिर से परिभाषित करता हूं।

इसमें एक छोटी सी समस्या है, क्योंकि यदि आप method_added के अंदर कोई विधि जोड़ते हैं, तो निश्चित रूप से इसे तुरंत फिर से बुलाया जाएगा और विधि फिर से जोड़ दी जाएगी, जिससे इसे फिर से बुलाया जाएगा, और इसी तरह। तो, मुझे रिकर्सन के खिलाफ सुरक्षा करने की जरूरत है।

ध्यान दें कि यह समाधान वास्तव में भी लागू करता है ग्राहक पर एक आदेश: जबकि ओपी के संस्करण केवल काम करता है अगर आप before_filterके बाद फोन विधि को परिभाषित करने, मेरे संस्करण ही काम करता है अगर आप इसे से पहले कहते हैं। हालांकि, यह विस्तार करना आसान है ताकि यह उस समस्या से पीड़ित न हो।

यह भी ध्यान रखें मैं कुछ अतिरिक्त परिवर्तन है कि समस्या से संबंधित नहीं हैं बनाया है, लेकिन है कि मुझे लगता है कि अधिक Rubyish हैं:

  • एक वर्ग के बजाय एक mixin का उपयोग करें: विरासत रूबी में एक बहुत ही मूल्यवान संसाधन है, क्योंकि आप केवल एक वर्ग से उत्तराधिकारी हो सकते हैं। हालांकि, मिक्सिन सस्ते हैं: आप जितना चाहें उतना मिश्रण कर सकते हैं। इसके अलावा: क्या आप वास्तव में कह सकते हैं कि होमवर्क आईएस-ए मेथड इंटरसेप्शन?
  • eval के बजाय Module#define_method का उपयोग करें: eval बुरा है। 'निफ ने कहा। (ओपी के कोड में, पहले स्थान पर eval का उपयोग करने के लिए बिल्कुल कोई कारण नहीं था।)
  • alias_method की बजाय विधि रैपिंग तकनीक का उपयोग करें: alias_method श्रृंखला तकनीक बेकार old_foo और old_bar विधियों के साथ नामस्थान को प्रदूषित करती है। मुझे अपने नामस्थान साफ ​​पसंद है।

मैं सिर्फ सीमाओं मैं उपर्युक्त में से कुछ तय है, और कुछ अधिक सुविधाओं को जोड़ा है, लेकिन भी मेरे स्पष्टीकरण के पुनर्लेखन के लिए आलसी हूँ, इसलिए मैं यहाँ संशोधित संस्करण repost:

module MethodInterception 
    def before_filter(*meths) 
    return @wrap_next_method = true if meths.empty? 
    meths.delete_if {|meth| wrap(meth) if method_defined?(meth) } 
    @intercepted_methods += meths 
    end 

    private 

    def wrap(meth) 
    old_meth = instance_method(meth) 
    define_method(meth) do |*args, &block| 
     puts 'before' 
     old_meth.bind(self).(*args, &block) 
     puts 'after' 
    end 
    end 

    def method_added(meth) 
    return super unless @intercepted_methods.include?(meth) || @wrap_next_method 
    return super if @recursing == meth 

    @recursing = meth # protect against infinite recursion 
    wrap(meth) 
    @recursing = nil 
    @wrap_next_method = false 

    super 
    end 

    def self.extended(klass) 
    klass.instance_variable_set(:@intercepted_methods, []) 
    klass.instance_variable_set(:@recursing, false) 
    klass.instance_variable_set(:@wrap_next_method, false) 
    end 
end 

class HomeWork 
    extend MethodInterception 

    def say_hello 
    puts 'say hello' 
    end 

    before_filter(:say_hello, :say_goodbye) 

    def say_goodbye 
    puts 'say goodbye' 
    end 

    before_filter 
    def say_ahh 
    puts 'ahh' 
    end 
end 

(h = HomeWork.new).say_hello 
h.say_goodbye 
h.say_ahh 
+0

यह सरल और निफ्टी है। – Swanand

+0

एक नोट: alias_method नामस्थान को प्रदूषित करता है लेकिन alias_method + भेजने का उपयोग विधि के संदर्भ (मेरे परीक्षण में लगभग 50% तेज) प्राप्त करने से तेज़ी से निष्पादन में होगा। –

0

जोर्ग डब्ल्यू मिट्टाग का समाधान बहुत अच्छा है। यदि आप कुछ और मजबूत चाहते हैं (अच्छी तरह से परीक्षण किया गया है) सबसे अच्छा संसाधन रेल कॉलबैक मॉड्यूल होगा।

+1

क्या उसने कहा कि वह रेल का उपयोग कर रहा था ?? – horseyguy

+0

मैं जोर्ग के उदाहरण में कोड की 50 से कम लाइनों की गणना करता हूं (होमवर्क क्लास शामिल)। निश्चित रूप से हम इसे परीक्षण करने की रणनीति के साथ आ सकते हैं जब तक कि हम इसे मजबूत और अच्छी तरह से परीक्षण न करें। –

+0

@banister: यह नहीं पता कि स्वानंद को उस पागल धारणा से कहाँ मिला। केवल 98% रूबी लोक रेल का उपयोग करते हैं। –

संबंधित मुद्दे