[英]In ruby, how to load raw source code to an instance of Proc?(add `before` `after` hook to class methods)
通常,我們可以使用Proc
對象執行此操作:
[15] pry(main)> pr = -> { puts "test message" }
=> #<Proc:0x000000057b8a90@(pry):12 (lambda)>
[16] pry(main)> pr.call
test message
=> nil
[17] pry(main)> define_method("test_method", pr)
=> :test_method
[18] pry(main)> test_method
test message
但是,如果我有原始代碼字符串並將其加載到proc中怎么辦? 偽造的代碼如下:
raw_code = "puts 'test message'"
pr = -> { load(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr}
test_method # => test message
實際上,我最初的問題是如何編寫這樣的方法掛鈎:
class TestClass
def test_method
puts url
end
def test_method_a
puts url
end
before :test_method, :test_method_a do
url = __method__.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
我的問題比較復雜,這只是說明關鍵問題的簡單示例。
如何定義
load
方法以使其正常工作?
通過將其拼寫為eval
:
raw_code = "puts 'test message'"
pr = -> { eval(raw_code) } # how to define the `load` method to get this working?
pr.call # => test message
define_method("test_method", pr)
test_method # => test message
--output:--
test message
test message
實際上,我最初的問題是如何編寫方法掛鈎...
class TestClass
def initialize
@url = %q{__method__.to_s.split("_").join("/")}
end
def test_method
puts(eval @url)
end
def test_method_a
puts(eval @url)
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
實際上,我最初的問題是如何編寫這樣的方法掛鈎:
Module TestClass def test_method puts url end
問題是url
永遠不能引用def之外的值。 def切斷def外部局部變量的可見性。
class TestClass
def test_method
puts @url
end
def test_method_a
puts @url
end
def self.before(*method_names, &block)
method_names.each do |method_name|
alias_method "first_#{method_name}", method_name
define_method(method_name, block) #This changes __method__ inside the block from nil to method_name
alias_method "second_#{method_name}", method_name
define_method(method_name) do
send "second_#{method_name}" #This method executes: @url = __method__.to_s.split(...
send "first_#{method_name}" #This method executes: puts @url
end
end
end
before :test_method, :test_method_a do
@url = __method__.to_s.split("_").join("/")
end
end
TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"
--output:--
test/method
test/method/a
短版: load
從文件加載代碼。 如果要運行字符串中已有的代碼,則可以使用eval
或它的一個朋友class_eval
或instance_eval
。
但是,如果最終使用eval
,則需要非常小心,以免意外運行可能刪除文件,安裝惡意軟件或執行任何操作的代碼。
較長的版本:對於具有load
的工作版本(在ruby 2.2.3中),您需要將TestClass
類放入文件中:
class TestClass
def test_method
puts "OHAI"
end
end
讓我們將此類保存在名為“ test_class.rb”的文件中。
這樣,以下內容應該可以正常工作:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
這不會解決您的“原始問題”,但可以使您對load
工作原理有更好的了解。
關於為什么我的問題method hook
與將load raw source code to Proc instance
問題可能引起很多困惑。
而且,現在我已經解決了我的問題。 所以,讓我詳細解釋整個事情:
1,我最初的問題來自於我需要在模塊的某些方法的頂部提取一堆重復的代碼,如下所示:
module TestClass
extend self
def test_method
url = __method__.to_s.split("_").join("/") # duplicated code,need to extract
puts url
end
def test_method_a
url = __method__.to_s.split("_").join("/") # duplicated code, need to extract
puts url
end
2,然后,經過一番思考,我想到了一個想法,那就是test_method
test_method_a
的源代碼,並通過添加url = __method__.to_s.split("_").join("/")
開頭,然后使用新代碼重新定義該方法。
3,經過大量的破解后,我使用eval
失敗,然后在此處發布請求幫助。
4,閱讀回復后,我確保eval
絕對是我想要的。
5,最后,我成功了,代碼顯示在這里:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar[1..-2].join("\n")
define_method(name, -> { eval(method_source_code) })
end
end
TestClass.test_method # => test/method
TestClass.test_method_a # => test/method/a
6,更簡潔的代碼版本顯示:
module TestClass
extend self
def test_method
puts url
end
def test_method_a
puts url
end
[ :test_method, :test_method_a ].each do |name|
method_source_code_ar = instance_method(name).source.split("\n")
method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')
method_source_code = method_source_code_ar.join("\n")
eval(method_source_code)
end
end
7,對於eval
,對我來說,我認為理解它的關鍵是,不認為您好像處於編寫模式,而是好像代碼逐行運行,直到到達eval
行代碼為止,到那時,該怎么辦?您想要代碼做什么? 當然,只需eval
method_source_code
字符串即可。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.