मैं सिर्फ इस के साथ आया था:
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
यह सरल और निफ्टी है। – Swanand
एक नोट: alias_method नामस्थान को प्रदूषित करता है लेकिन alias_method + भेजने का उपयोग विधि के संदर्भ (मेरे परीक्षण में लगभग 50% तेज) प्राप्त करने से तेज़ी से निष्पादन में होगा। –