在 ruby 中,如何将原始源代码加载到 Proc 的实例?(将 `before` `after` 钩子添加到 class 方法)
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
但是,如果我有原始代码字符串并想将其加载到过程中怎么办?下面是假代码:
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
,或者它的朋友之一,class_eval
或 instance_eval
.
如果您最终使用 eval
,但是,您需要非常小心,以免您不小心 运行 代码删除您的文件、安装恶意软件或其他任何东西。
更长的版本:对于 load
的工作版本(在 ruby 2.2.3 中),您需要将 TestClass
class 放入文件中:
class TestClass
def test_method
puts "OHAI"
end
end
让我们将此 class 保存在名为 "test_class.rb" 的文件中。
有了这个,下面的应该就可以了:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
这不会解决您的 "original problem",但它应该能让您更好地理解 load
的工作原理。
how to define the load
method to get this working?
通过拼写 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
Actually, my original problem is how to write a method hook...
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
Actually, my original problem is how to write a method hook like this:
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
阅读回复后,我自己的解决方案和更多详细信息显示在此处:
也许有很多人不明白为什么我的问题 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
失败,然后,post 在这里寻求帮助。
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
字符串。
通常,我们可以用 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
但是,如果我有原始代码字符串并想将其加载到过程中怎么办?下面是假代码:
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
,或者它的朋友之一,class_eval
或 instance_eval
.
如果您最终使用 eval
,但是,您需要非常小心,以免您不小心 运行 代码删除您的文件、安装恶意软件或其他任何东西。
更长的版本:对于 load
的工作版本(在 ruby 2.2.3 中),您需要将 TestClass
class 放入文件中:
class TestClass
def test_method
puts "OHAI"
end
end
让我们将此 class 保存在名为 "test_class.rb" 的文件中。
有了这个,下面的应该就可以了:
pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method
这不会解决您的 "original problem",但它应该能让您更好地理解 load
的工作原理。
how to define the
load
method to get this working?
通过拼写 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
Actually, my original problem is how to write a method hook...
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
Actually, my original problem is how to write a method hook like this:
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
阅读回复后,我自己的解决方案和更多详细信息显示在此处:
也许有很多人不明白为什么我的问题 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
失败,然后,post 在这里寻求帮助。
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
字符串。